iOS 隐私清单怎么写?PrivacyInfo.xcprivacy 隐私清单示例
最后更新
iOS 隐私清单是放在 App 包根目录的 PrivacyInfo.xcprivacy 文件,用来声明 App 收集哪些数据、是否跟踪用户,以及为什么调用 UserDefaults、文件时间戳这类「必需原因 API」。只要代码里用了这类 API(几乎所有 App 都会用 UserDefaults),上架时就必须有它,否则上传会收到 ITMS-91053 并被拒收。做法是按源码里命中的 API 类别逐类写原因码,加入 App 的 target 后解压 ipa 核对。规则以 Apple 官方文档为准,截至 2026 年 10 月。
开始前准备好能构建 Release 包的工程,装好 KXBuild 和 VS Code 插件,还没装的按安装页操作。
什么时候必须要 iOS 隐私清单?
隐私清单在 WWDC23 推出,之后 Apple 分几步把它变成上架要求。
| 生效日期 | 要求 | 不满足时 | 官方出处 |
|---|---|---|---|
| 2024 年 5 月 1 日 | 用到必需原因 API 的 App 要在隐私清单里写明原因 | App Store Connect 不接受上传,邮件提示 ITMS-91053 | Describing use of required reason API |
| 2024 年 11 月 12 日 | 提交审核的 App 里的隐私清单必须有效 | 邮件提示 ITMS-91056,列出无效文件路径 | TN3181 |
| 2025 年 2 月 12 日 | 常用第三方 SDK 必须带有效的隐私清单 | 邮件提示 ITMS-91061,列出缺清单的 SDK | Adding a privacy manifest |
简单说,2026 年上架的 iOS App,只要用了 UserDefaults(包括 SwiftUI 的 @AppStorage),就要有隐私清单。自己的代码一个这类 API 都没用、也不收集数据的,可以不放,但用到的第三方 SDK 仍要各自带清单。
清单里两部分的适用范围不同。收集的数据类型在所有平台都要写;必需原因 API 在 iOS、iPadOS、tvOS、visionOS、watchOS 上要写。
PrivacyInfo.xcprivacy 文件结构是什么样的?
PrivacyInfo.xcprivacy 是一个 plist,文件名必须是这个,顶层字典里有四个键。
| 键 | 类型 | 作用 |
|---|---|---|
NSPrivacyTracking |
Boolean | App 是否按 App Tracking Transparency 的定义把数据用于跟踪 |
NSPrivacyTrackingDomains |
Array(String) | 用于跟踪的网络域名。只有 NSPrivacyTracking 为 true 时才写 |
NSPrivacyCollectedDataTypes |
Array(Dictionary) | 收集的数据类型,每种一个字典 |
NSPrivacyAccessedAPITypes |
Array(Dictionary) | 用到的必需原因 API 类别,每类一个字典 |
NSPrivacyTrackingDomains 里列出的域名,在用户没有授权跟踪时请求会失败。域名只写主域或子域,不带路径、查询参数和末尾斜杠。
NSPrivacyAccessedAPITypes 里每个字典有两个键。
NSPrivacyAccessedAPIType:String,API 类别名,例如NSPrivacyAccessedAPICategoryUserDefaults。NSPrivacyAccessedAPITypeReasons:Array,这个类别下的原因码,例如CA92.1。
NSPrivacyCollectedDataTypes 里每个字典有四个键。
NSPrivacyCollectedDataType:String,数据类型,例如NSPrivacyCollectedDataTypeEmailAddress。NSPrivacyCollectedDataTypeLinked:Boolean,这项数据是否和用户身份关联。NSPrivacyCollectedDataTypeTracking:Boolean,这项数据是否用于跟踪。NSPrivacyCollectedDataTypePurposes:Array,收集目的,例如NSPrivacyCollectedDataTypePurposeAppFunctionality。
必需原因 API 有哪五类?
Apple 把可能被用来做设备指纹的 API 分成五类,下表是每类包含的 API 和常用原因码,原因码的含义按官方 NSPrivacyAccessedAPIType 文档整理。
| 类别 | 包含的 API(节选) | 常用原因码 |
|---|---|---|
NSPrivacyAccessedAPICategoryUserDefaults |
UserDefaults,包括 @AppStorage |
CA92.1、1C8F.1 |
NSPrivacyAccessedAPICategoryFileTimestamp |
creationDate、modificationDate、contentModificationDateKey、creationDateKey、stat、fstat、lstat、getattrlist 等 |
C617.1、DDA9.1、3B52.1 |
NSPrivacyAccessedAPICategorySystemBootTime |
systemUptime、mach_absolute_time() |
35F9.1、8FFB.1 |
NSPrivacyAccessedAPICategoryDiskSpace |
volumeAvailableCapacityKey、volumeAvailableCapacityForImportantUsageKey、volumeTotalCapacityKey、systemFreeSize、statfs、statvfs 等 |
E174.1、85F4.1 |
NSPrivacyAccessedAPICategoryActiveKeyboards |
activeInputModes |
54BD.1、3EC4.1 |
原因码怎么选
按你实际的用法选,可以写多个,但每个都要和 App 的功能一致。下面是官方原文的要点。
| 原因码 | 类别 | 适用场景 | 限制 |
|---|---|---|---|
CA92.1 |
UserDefaults | 读写只有 App 自己能访问的数据 | 不能读其他 App 或系统写的数据 |
1C8F.1 |
UserDefaults | 读写同一 App Group 内 App、扩展、App Clip 共享的数据 | 不能读写 App Group 以外的数据 |
C617.1 |
文件时间戳 | 读取 App 容器、App Group 容器或 App 的 CloudKit 容器内文件的时间戳、大小等元数据 | 只限这些容器内的文件 |
DDA9.1 |
文件时间戳 | 把文件时间戳显示给用户 | 数据不能发出设备 |
3B52.1 |
文件时间戳 | 访问用户明确授权的文件或目录(例如通过文档选择器)的元数据 | 只限用户授权的文件 |
35F9.1 |
系统启动时间 | 计算 App 内事件之间的间隔,或实现计时器 | 只有事件间隔可以发出设备 |
8FFB.1 |
系统启动时间 | 为 App 内事件(例如 UIKit、AVFAudio 事件)计算绝对时间戳 | 启动时间本身不能发出设备 |
E174.1 |
磁盘空间 | 检查空间是否够写文件,或空间不足时删除文件 | App 的行为要随空间变化且用户能察觉 |
85F4.1 |
磁盘空间 | 把磁盘空间显示给用户 | 数据不能发出设备 |
54BD.1 |
活动键盘 | 根据当前键盘给用户展示正确的自定义界面 | App 要有文本输入框,并随键盘变化表现出可察觉的差异 |
3EC4.1 |
活动键盘 | App 本身是自定义键盘 App | 提供系统级键盘必须是主要功能 |
C56D.1、0A2A.1 这类「SDK 包装函数」原因码只能由第三方 SDK 声明,App 自己的清单不要用。
多数 App 只需要两三项。记住登录状态、设置项用 CA92.1;清理缓存时读文件修改时间用 C617.1;下载前检查剩余空间用 E174.1;统计页面停留时长用 35F9.1。
找出自己用了哪些
在工程根目录搜一遍源码,看命中哪些 API。Windows 上在 PowerShell 执行下面的命令。
1Get-ChildItem -Recurse -Include *.swift,*.m,*.mm,*.c,*.cpp |
2 Select-String -Pattern 'UserDefaults|@AppStorage|creationDate|modificationDate|contentModificationDateKey|systemUptime|mach_absolute_time|volumeAvailableCapacity|volumeTotalCapacity|systemFreeSize|statfs|statvfs|activeInputModes|\bstat\(|\blstat\(|\bfstat\(' |
3 Select-Object Path, LineNumber, Line
Linux 或 WSL 上用 grep。
1grep -rnE --include='*.swift' --include='*.m' --include='*.mm' --include='*.c' --include='*.cpp' \
2 'UserDefaults|@AppStorage|creationDate|modificationDate|contentModificationDateKey|systemUptime|mach_absolute_time|volumeAvailableCapacity|volumeTotalCapacity|systemFreeSize|statfs|statvfs|activeInputModes|\bstat\(|\blstat\(|\bfstat\(' .
搜索范围只放你自己的源码目录,Pods、.build、node_modules 里的第三方代码由各 SDK 自己的清单负责。命中一类就在清单里加一个字典。上传后邮件提示的类别如果比你声明的多,多出来的通常来自某个第三方 SDK。
隐私清单完整示例怎么写?
下面是一个常见 App 的完整清单:不跟踪用户;收集邮箱用于登录,和用户身份关联;收集崩溃数据用于分析;代码里用到 UserDefaults、文件时间戳、系统启动时间和磁盘空间。
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3<plist version="1.0">
4<dict>
5 <key>NSPrivacyTracking</key>
6 <false/>
7 <key>NSPrivacyCollectedDataTypes</key>
8 <array>
9 <dict>
10 <key>NSPrivacyCollectedDataType</key>
11 <string>NSPrivacyCollectedDataTypeEmailAddress</string>
12 <key>NSPrivacyCollectedDataTypeLinked</key>
13 <true/>
14 <key>NSPrivacyCollectedDataTypeTracking</key>
15 <false/>
16 <key>NSPrivacyCollectedDataTypePurposes</key>
17 <array>
18 <string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
19 </array>
20 </dict>
21 <dict>
22 <key>NSPrivacyCollectedDataType</key>
23 <string>NSPrivacyCollectedDataTypeCrashData</string>
24 <key>NSPrivacyCollectedDataTypeLinked</key>
25 <false/>
26 <key>NSPrivacyCollectedDataTypeTracking</key>
27 <false/>
28 <key>NSPrivacyCollectedDataTypePurposes</key>
29 <array>
30 <string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
31 </array>
32 </dict>
33 </array>
34 <key>NSPrivacyAccessedAPITypes</key>
35 <array>
36 <dict>
37 <key>NSPrivacyAccessedAPIType</key>
38 <string>NSPrivacyAccessedAPICategoryUserDefaults</string>
39 <key>NSPrivacyAccessedAPITypeReasons</key>
40 <array>
41 <string>CA92.1</string>
42 </array>
43 </dict>
44 <dict>
45 <key>NSPrivacyAccessedAPIType</key>
46 <string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
47 <key>NSPrivacyAccessedAPITypeReasons</key>
48 <array>
49 <string>C617.1</string>
50 </array>
51 </dict>
52 <dict>
53 <key>NSPrivacyAccessedAPIType</key>
54 <string>NSPrivacyAccessedAPICategorySystemBootTime</string>
55 <key>NSPrivacyAccessedAPITypeReasons</key>
56 <array>
57 <string>35F9.1</string>
58 </array>
59 </dict>
60 <dict>
61 <key>NSPrivacyAccessedAPIType</key>
62 <string>NSPrivacyAccessedAPICategoryDiskSpace</string>
63 <key>NSPrivacyAccessedAPITypeReasons</key>
64 <array>
65 <string>E174.1</string>
66 </array>
67 </dict>
68 </array>
69</dict>
70</plist>
按自己的情况删改。
- 只用了 UserDefaults 的,
NSPrivacyAccessedAPITypes里只留第一个字典。 - 什么数据都不收集的,
NSPrivacyCollectedDataTypes写成空数组<array/>。 - 某个类别不再使用,就删掉整个字典,不要留下原因码为空的字典。
NSPrivacyAccessedAPITypes一项都不剩时,连这个键一起删掉。 - 用了广告或归因 SDK 跟踪用户的,
NSPrivacyTracking改成<true/>,并加上NSPrivacyTrackingDomains列出跟踪域名,数组不能为空。
常用的数据类型值还有 NSPrivacyCollectedDataTypeName(姓名)、NSPrivacyCollectedDataTypePhoneNumber(电话)、NSPrivacyCollectedDataTypeUserID(用户 ID)、NSPrivacyCollectedDataTypeDeviceID(设备 ID)、NSPrivacyCollectedDataTypePreciseLocation(精确位置)、NSPrivacyCollectedDataTypeProductInteraction(产品交互)。
收集目的还有 NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising、NSPrivacyCollectedDataTypePurposeDeveloperAdvertising、NSPrivacyCollectedDataTypePurposeProductPersonalization、NSPrivacyCollectedDataTypePurposeOther。
完整列表见官方数据类型说明。
怎么把隐私清单加进工程?
隐私清单要作为 App target 的资源打进包,构建后位于 .app 根目录。
新建文件
- 在 VS Code 资源管理器里,到 App 的源码文件夹(和
Info.plist、Assets.xcassets同级的那个)新建文件PrivacyInfo.xcprivacy。 - 把上一节的 XML 粘进去,按自己的情况删改后保存。
- 以后再改,双击这个文件,用 plist 编辑器以表格方式编辑。字典和数组能展开折叠,右键「添加子项」给数组加原因码,类型在每行的类型列里选。保存格式保持 XML,方便代码评审时看差异。
加入 App 的 target
新文件要属于 App 的 target 才会编进包里。
- 工程用的是 Xcode 16 起的文件夹同步(Xcode 里显示为蓝色文件夹)时,文件放进源码文件夹就已经加入,不用额外操作。
- KXBuild 模板和较老的工程用的是分组,执行下面的命令登记一次。参数说明见 kxbuild 命令参考。
1kxbuild project add-resource -f MyApp/PrivacyInfo.xcprivacy -t MyApp -P .\MyApp.xcodeproj
工程里有多个 App target(例如正式版和测试版分开的),每个要上架的 target 都执行一次。小组件等扩展里用到必需原因 API 的,扩展的 bundle 也要有自己的清单,用 -t 指定扩展的 target 再登记一份。
Swift Package 里的清单
你自己维护的 Swift Package 要随包带清单时,把文件放在 Sources/<Target名>/ 下,并在 Package.swift 里显式声明为资源,否则不会被当作资源处理。
1targets: [
2 .target(
3 name: "SomeLibrary",
4 resources: [
5 .process("PrivacyInfo.xcprivacy")
6 ]
7 ),
8]
构建后核对
构建 App Store 包,在 VS Code 运行「KXBuild: 生成 App Store ipa」,或用命令行。
1kxbuild build .\MyApp.xcodeproj -c Release --export app-store
ipa 本质是 zip 包,应用在 Payload/<App>.app/ 下。Windows 10 及以上自带 tar,可以直接列出包内的隐私清单。
1tar -tf .\build\MyApp.ipa | findstr PrivacyInfo.xcprivacy
Linux 或 WSL 上用 unzip。
1unzip -l build/MyApp.ipa | grep PrivacyInfo.xcprivacy
输出里要有 Payload/MyApp.app/PrivacyInfo.xcprivacy 这一行,这是 App 自己的清单。其他行通常是各框架和资源包自带的清单,例如 Payload/MyApp.app/Frameworks/Xxx.framework/PrivacyInfo.xcprivacy。
想确认打进包的内容就是你写的那份,解压出来查看。
1mkdir .\ipa-check
2tar -xf .\build\MyApp.ipa -C .\ipa-check
3code .\ipa-check\Payload\MyApp.app\PrivacyInfo.xcprivacy
没有出现 App 根目录那一行时,按顺序检查三处。
- 文件名是否正好是
PrivacyInfo.xcprivacy,大小写和扩展名都要一致。 - 文件是否已加入 App 的 target,分组工程重新执行一次
kxbuild project add-resource。 - 文件是否放在了构建排除的目录里,挪回 App 源码文件夹。
第三方 SDK 的隐私清单怎么检查?
哪些 SDK 必须带
Apple 维护一份常用第三方 SDK 名单,名单上的 SDK 不论哪个版本都要带隐私清单,以二进制形式引入的还要带签名。
名单里包括 Alamofire、AFNetworking、Firebase 系列、FBSDKCoreKit、GoogleSignIn、Kingfisher、SDWebImage、Lottie、SnapKit、RxSwift、Realm、OneSignal、Flutter、hermes、Capacitor、Cordova、UnityFramework,以及 shared_preferences_ios、path_provider、url_launcher、image_picker_ios 等一批 Flutter 插件。
新 App 包含名单上的 SDK,或更新版本新加入名单上的 SDK 时,缺清单会收到 ITMS-91061。
名单以外的 SDK,只要用了必需原因 API、收集用户数据或连接跟踪域名,也应该自带清单。
怎么查
- 用上一节的
tar -tf命令列出包里所有的PrivacyInfo.xcprivacy。 - 再列出包里的框架,对照名单看哪些框架下面没有清单。
1tar -tf .\build\MyApp.ipa | findstr /R "\.framework/Info\.plist$"
- 缺清单的,按依赖方式升级。CocoaPods 改
Podfile里的版本后重新pod install;Swift Package 改好版本要求后用kxbuild project resolve --force重新解析;Flutter 插件更新pubspec.yaml里的版本;React Native 和 Expo 升级对应的 npm 包后重新生成 iOS 工程。
以静态库(.a)形式提供的 SDK 无法携带资源,只能等提供方改成带清单的静态框架或 XCFramework。Apple 明确建议联系 SDK 提供方获取新版本,不要把 SDK 的隐私行为写进你自己的清单。
上传报 ITMS-91053、ITMS-91056 怎么办?
| 错误码 | 含义 | 处理 |
|---|---|---|
| ITMS-91053 | 缺少必需原因 API 声明,邮件列出 API 类别 | 自己代码用的,在 App 清单里加对应类别和原因码;来自 SDK 的,升级 SDK |
| ITMS-91061 | 名单上的第三方 SDK 缺少隐私清单 | 按邮件给出的 SDK 名升级到带清单的版本 |
| ITMS-91056 | 隐私清单无效,邮件给出文件路径 | 检查键名拼写、值类型、原因码是否属于对应类别 |
| ITMS-90683 | Info.plist 缺少权限用途说明 | 这是 Info.plist 的问题,不是隐私清单,补上对应的 NS…UsageDescription |
ITMS-91056 最常见的写法错误来自 TN3181。
NSPrivacyTracking不是 Boolean。NSPrivacyTracking为 true,但NSPrivacyTrackingDomains是空数组。NSPrivacyTracking为 false,却写了NSPrivacyTrackingDomains。NSPrivacyAccessedAPIType的类别名拼错,或原因码不属于这个类别,例如把C617.1写进了 UserDefaults 类别。NSPrivacyAccessedAPITypeReasons是空数组,或不是数组。
其他上传错误码见 ITMS 错误码速查表。
隐私清单和隐私营养标签
App Store 页面上的「App 隐私」标签(常叫隐私营养标签)来自你在 App Store Connect「App 隐私」页填写的问卷,不会从隐私清单自动生成。两者要说的是同一件事,内容必须一致。
| 隐私清单 | App 隐私问卷(营养标签) | |
|---|---|---|
| 在哪里 | 随 ipa 打进包里 | App Store Connect 网页上填写 |
| 谁来写 | App 和每个 SDK 各写一份 | 你为整个 App 填一份 |
| 内容 | 数据类型、跟踪、必需原因 API | 数据类型、用途、是否关联身份、是否跟踪 |
| 谁会看到 | Apple 的上传检查 | App Store 用户 |
填问卷时,把 App 清单和每个 SDK 清单里的 NSPrivacyCollectedDataTypes 合并起来,逐项对应到问卷里的数据类型和用途。SDK 的数据也要算进 App 的标签里。必需原因 API 只写在清单里,问卷里没有对应项。
上架前检查清单
- App 源码文件夹里有
PrivacyInfo.xcprivacy,文件名大小写正确。 - 源码里命中的每类必需原因 API,清单里都有对应字典和原因码,没有空数组。
NSPrivacyTracking和NSPrivacyTrackingDomains搭配正确。- 每个要上架的 App target 都登记了清单,用到这类 API 的扩展也有自己的清单。
tar -tf能看到Payload/<App>.app/PrivacyInfo.xcprivacy。- 名单上的第三方 SDK 都已升级到带清单的版本。
- App Store Connect 的「App 隐私」问卷和所有清单里的数据收集一致。
这几项都勾完,再用 kxbuild build -c Release --export app-store 打包上传,iOS 隐私清单相关的 ITMS-91053、ITMS-91056、ITMS-91061 就不会再出现。
常见问题
不加 PrivacyInfo.xcprivacy 能上架 App Store 吗?
代码里用了 UserDefaults 等必需原因 API 的不行。自 2024 年 5 月 1 日起,App Store Connect 不接受没有声明这类 API 原因的 App。确实一个都没用、也不收集数据的 App 可以不放,但第三方 SDK 仍要自带清单。
只用了 @AppStorage,隐私清单要写什么?
@AppStorage 底层就是 UserDefaults。在 NSPrivacyAccessedAPITypes 里加一个字典,类别写 NSPrivacyAccessedAPICategoryUserDefaults,原因码写 CA92.1。和小组件通过 App Group 共享数据的,再加上 1C8F.1。
第三方 SDK 的 API 用法要写进我自己的隐私清单吗?
不要。Apple 规定 SDK 的用法由 SDK 自己的清单声明,App 的清单不能代替。收到指向 SDK 的 ITMS-91053 或 ITMS-91061,把这个 SDK 升级到带清单的版本。
在 Windows 上怎么确认隐私清单打进了 ipa?
在 PowerShell 执行 tar -tf .\build\MyApp.ipa | findstr PrivacyInfo.xcprivacy,输出里有 Payload/MyApp.app/PrivacyInfo.xcprivacy 就说明已打进 App 根目录。没有的话,检查文件名并用 kxbuild project add-resource 把它加入 App 的 target。
隐私清单和 App Store Connect 的 App 隐私问卷有什么区别?
隐私清单随包提交给 Apple 检查,App 隐私问卷决定商店页面上用户看到的隐私标签。问卷要手动填写,内容要和 App 及所有 SDK 清单里声明的数据收集保持一致。