blog
blog copied to clipboard
2024年5月1号起,苹果新政:“Describing use of required reason API”
最近苹果又双叒叕出了新政了,关于 Describing use of required reason API 的问题,2024年5月1号之后,所有用到这些接口的(其实一般都要用到,比如本地存储 UserDefaults
),都需要新建一个 PrivacyInfo.xcprivacy
文件,并把使用到的 API 和相关信息填上。
PS: Widget 项目也需要单独添加!
PrivacyInfo.xcprivacy
文件添加
参考:https://developer.apple.com/documentation/bundleresources/privacy_manifest_files#4284009
解决
方法一:打个包上去提审一次,会驳回并收到包含 ISSUE 邮件
5月1号前提审的应用,会收到如下的提醒邮件,但不影响当次的审核。邮件里面会列出来你哪个包里有哪些API需要声明权限,如下:
再查看文档 https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api 找出对应接口的可选值,按要求填上即可。
方法二:自主扫描
网上有大神已经提供了一个解决方案:ios_17_required_reason_api_scanner
通过以上提供的脚本文件 xxx.sh
,可以很快速的搜索到你的项目所用到的 required reason API
。
有两个脚本:required_reason_api_text_scanner.sh
和 required_reason_api_scanner_binary.sh
,一个是代码搜索,一个是二进制文件搜索。可以两个都跑下,确保没有遗漏。
比如,下面是我用 required_reason_api_scanner_binary.sh
扫描的结果:
结果显示用到了两个:NSURLCreationDateKey
和 NSUserDefaults
,再查看文档 https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api 找出对应接口的可选值,按要求填上即可。
PrivacyInfo.xcprivacy 例子
下面提供一个我项目中 PrivacyInfo.xcprivacy
的例子,仅供参考:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>1C8F.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>E174.1</string>
</array>
</dict>
</array>
</dict>
</plist>
Property List 版本:
注意,红色框内的值,需要根据项目实际用途选填具体的值!!!
脚本备份
为了防止上面的脚本项目被删,下面给手动备份一下(最新手动更新时间:2024.04.08)。
required_reason_api_text_scanner.sh
#!/bin/bash
# Array of directories to exclude from the search
excluded_dirs=() # e.g. ("Pods" "3rdparty")
# Global variable for search strings that may indicate a use of "iOS required reason API"
# taken from here: https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api
search_string=(".creationDate"
".creationDateKey"
".modificationDate"
".fileModificationDate"
".contentModificationDateKey"
".creationDateKey"
"getattrlist("
"getattrlistbulk("
"fgetattrlist("
"stat.st_" # see https://developer.apple.com/documentation/kernel/stat
"fstat("
"fstatat("
"lstat("
"getattrlistat("
"systemUptime"
"mach_absolute_time()"
"volumeAvailableCapacityKey"
"volumeAvailableCapacityForImportantUsageKey"
"volumeAvailableCapacityForOpportunisticUsageKey"
"volumeTotalCapacityKey"
"systemFreeSize"
"systemSize"
"statfs("
"statvfs("
"fstatfs("
"fstatvfs("
"getattrlist("
"fgetattrlist("
"getattrlistat("
"activeInputModes"
"UserDefaults"
"NSUserDefaults"
"NSFileCreationDate"
"NSFileModificationDate"
"NSFileSystemFreeSize"
"NSFileSystemSize"
"NSURLContentModificationDateKey"
"NSURLCreationDateKey"
"NSURLVolumeAvailableCapacityForImportantUsageKey"
"NSURLVolumeAvailableCapacityForOpportunisticUsageKey"
"NSURLVolumeAvailableCapacityKey"
"NSURLVolumeTotalCapacityKey"
"AppStorage"
)
# Function to search for required reason API strings in a Swift files
search_in_swift_file() {
local file="$1"
# Loop through each search string
for string in "${search_string[@]}"; do
# Search for the string in the file and get the line numbers
lines=$(grep -n "$string" "$file" | cut -d ":" -f 1)
if [ -n "$lines" ]; then
echo "Found potentially required reason API usage '$string' in '$file'"
one_line_string=$(echo "$lines" | tr '\n' ' ')
echo "Line numbers: $one_line_string"
fi
done
}
# Function to check if a directory is in the excluded list
is_excluded_dir() {
local dir_name="$1"
for excluded_dir in "${excluded_dirs[@]}"; do
if [ "$dir_name" == "$excluded_dir" ]; then
return 0
fi
done
return 1
}
# Function to traverse directories recursively and search for Swift files
traverse_and_search() {
local folder="$1"
# Get the name of the current directory
local dir_name=$(basename "$folder")
# Check if the directory is in the excluded list
if is_excluded_dir "$dir_name"; then
return
fi
# Loop through each item in the folder
for item in "$folder"/*; do
if [ -d "$item" ]; then
# If it's a directory, call the function recursively
traverse_and_search "$item"
elif [ -f "$item" ] && ([[ "$item" == *.swift ]] || [[ "$item" == *.m ]] || [[ "$item" == *.h ]]); then
# If it's a file with .swift extension, search for the strings
search_in_swift_file "$item"
fi
done
}
# Check if a directory path is provided as an argument
if [ -z "$1" ]; then
echo "Usage: $0 <directory-path>"
exit 1
fi
# Start the search
echo "Searching for use of required reason API"
echo "See https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api"
traverse_and_search "$1"
required_reason_api_binary_scanner.sh
#!/bin/bash
set -e
symbols=(
activeInputModes
fgetattrlist
fstat
fstatat
fstatfs
fstatvfs
getattrlist
getattrlistat
getattrlistbulk
lstat
mach_absolute_time
NSFileCreationDate
NSFileModificationDate
NSFileSystemFreeSize
NSFileSystemSize
NSURLContentModificationDateKey
NSURLCreationDateKey
NSURLVolumeAvailableCapacityForImportantUsageKey
NSURLVolumeAvailableCapacityForOpportunisticUsageKey
NSURLVolumeAvailableCapacityKey
NSURLVolumeTotalCapacityKey
NSUserDefaults
stat
statfs
statvfs
systemUptime
)
function join_by {
local d=${1-} f=${2-}
if shift 2; then
printf %s "$f" "${@/#/$d}"
fi
}
function search_binaries_with_directory {
extension=$1
tempfile=$(mktemp)
trap "rm -f $tempfile" EXIT
find . -iname "*.$extension" -print0 > "$tempfile"
while IFS= read -r -d '' binary; do
name=$(basename "$binary")
binary_name=$(echo "$name" | sed "s/.$extension//")
binaries+=("$(dirname "$binary")/${name}/${binary_name}")
done < "$tempfile"
}
function search_binaries_with_filename {
extension=$1
tempfile=$(mktemp)
trap "rm -f $tempfile" EXIT
find . -iname "*.$extension" -print0 > "$tempfile"
while IFS= read -r -d '' binary; do
binaries+=("$binary")
done < "$tempfile"
}
cd "$1"
declare -a binaries
# Adds the .app binary
search_binaries_with_directory app
# Adds the (dynamic/static) .framework binaries
search_binaries_with_directory framework
# Adds the static libs .a binaries
search_binaries_with_filename a
echo "Analyzing binaries: ${binaries[@]}"
echo '---'
for binary in "${binaries[@]}"; do
if ! [ -f "$binary" ]; then
echo "binary '$binary' doesn't exist"
exit 1
fi
used_symbols=()
for symbol in "${symbols[@]}"; do
if nm "$binary" 2>/dev/null | xcrun swift-demangle | grep -E "$symbol$" >/dev/null; then
used_symbols+=($symbol)
fi
done
if [ ${#used_symbols[@]} -gt 0 ]; then
echo "Used symbols in binary $binary: $(join_by ', ' ${used_symbols[@]})"
fi
done