ITMS-91053 报错怎么解决(Expo 隐私清单)
作者:Mehdi Azizi
发布于 ,8分钟
ITMS-91053:构建用了需声明理由的 API 却没声明。在 ios.privacyManifests 加上类别和理由代码,再重新构建。
ITMS-91053 是 Apple 的自动检查,在你提交构建之后通过邮件发来。它的意思是:你的 app 代码用到了“需声明理由”的 API(偏好设置、文件时间戳、开机后经过的时间、磁盘空间或键盘),却没有在隐私清单里说明原因。用 Expo 的话,修复在 ios.privacyManifests 里完成:填上 Apple 指出的类别、描述你真实用途的代码,然后重新构建。
在 Solys(我那款带小游戏的闹钟)里,这些理由都写在 app 的配置中。下面是它的真实声明、列出依赖所用 API 的命令,以及提交前对最终文件要做的检查。
ITMS-91053 错误是什么意思?
邮件标题是“ITMS-91053: Missing API declaration”。它指出你构建中某个具体文件的代码引用了一个或多个需要声明理由的 API,然后列出涉及的类别,比如 NSPrivacyAccessedAPICategoryUserDefaults。
这些 API 不需要用户授权。但它们可以用来识别一台设备,也就是 Apple 所说的 fingerprinting(设备指纹)。这是被禁止的,即使用户同意了跟踪也不行。Apple 在关于需声明理由的 API 的页面里解释了:每个 app 和每个 SDK 都必须声明使用它们的原因。
同一页面定下了规则:**自 2024 年 5 月 1 日起,App Store Connect 不再接受没有在清单中说明这些用途的 app。**有开发者反映,这封邮件只是一个警告,构建照样被接受了。别指望这个:把它当成阻断问题,在下次提交前修好。
和其他 ITMS 错误一样,这不是审核员的拒绝。没人打开过你的 app:是 Apple 分析了二进制文件。
iOS 隐私清单:PrivacyInfo.xcprivacy 文件
隐私清单是一个名为 PrivacyInfo.xcprivacy 的文件,放在 app bundle 的根目录。根据 Apple 关于隐私清单的文档,它描述收集的数据、跟踪行为和需声明理由的 API。后者放在 NSPrivacyAccessedAPITypes 数组里:每个类别一项,包含类型和理由列表。
容易踩坑的一点:**一个清单只覆盖它自己的 bundle。**Apple 写得很明确:每个使用这些 API 的可执行文件或动态库,都必须在自己的 bundle 里带一份清单。你 app 的清单覆盖不了内嵌的 framework,SDK 也不能依赖你 app 的清单。
别把它和 ITMS-90683 要求的 Info.plist 说明文字搞混。那些文字会在权限请求里展示给用户。而隐私清单只有 Apple 会读。
5 类 API 及其理由代码
类别的完整名称以 NSPrivacyAccessedAPICategory 开头。API 和理由的列表见 NSPrivacyAccessedAPIType 这个键的页面,查阅日期为 2026 年 10 月 15 日。
| 类别 | 典型 API | 常见理由 |
|---|---|---|
| UserDefaults | UserDefaults | CA92.1 |
| FileTimestamp | creationDate | C617.1 |
| SystemBootTime | systemUptime | 35F9.1 |
| DiskSpace | volumeAvailableCapacityKey | E174.1 |
| ActiveKeyboards | activeInputModes | 54BD.1 |
你最常遇到的代码:
- CA92.1:app 读写自己的偏好设置,其他 app 无法访问。
- 1C8F.1:与同一 App Group 中的小组件或扩展共享偏好设置。
- C617.1:存放在 app 容器内的文件的日期或大小。
- 3B52.1:用户选择的文件,比如通过文档选择器。
- 35F9.1:测量 app 内两个事件之间经过的时间,或运行计时器。
- E174.1:写入文件前检查剩余空间,且对用户有可见的效果。
- 85F4.1:向用户显示磁盘空间。
**声明与你真实用途相符的理由,而不是看起来范围最广的那个。**Apple 明确说明,这些 API 及其数据只能用于所声明的理由,绝不能用于跟踪。有两个代码是第三方 SDK 专用的:C56D.1 和 0A2A.1。它们不该出现在你 app 的清单里。
在 Expo 中声明隐私清单
用 Expo,你不用自己写这个文件。你在 app.json 或 app.config 的 expo.ios.privacyManifests 下描述它,prebuild 会生成 PrivacyInfo.xcprivacy。这是 Expo 隐私清单文档里给出的做法。
下面是 Solys 配置的真实片段。这个键里还包含跟踪和收集数据的部分,这里删掉了:
{
"expo": {
"ios": {
"privacyManifests": {
"NSPrivacyAccessedAPITypes": [
{
"NSPrivacyAccessedAPIType": "NSPrivacyAccessedAPICategoryUserDefaults",
"NSPrivacyAccessedAPITypeReasons": ["CA92.1", "1C8F.1"]
},
{
"NSPrivacyAccessedAPIType": "NSPrivacyAccessedAPICategoryFileTimestamp",
"NSPrivacyAccessedAPITypeReasons": ["C617.1"]
},
{
"NSPrivacyAccessedAPIType": "NSPrivacyAccessedAPICategorySystemBootTime",
"NSPrivacyAccessedAPITypeReasons": ["35F9.1"]
}
]
}
}
}
}
CA92.1、C617.1 和 35F9.1 沿用了 React Native 和 app 中 Expo 模块的声明。1C8F.1 则来自 Solys 本身:app 通过 App Group 和它的小组件共享设置。
一个要知道的坑:如果你的 ios 目录是自动生成并被 git 忽略的,**手动写进这个目录的 PrivacyInfo.xcprivacy 永远不会进入构建。**EAS 会重新运行 prebuild,重新生成这个目录。2026 年 9 月 9 日我在自己的一个项目上亲眼见过:手写的清单在一次 prebuild 之后就没了。声明必须写在配置里。
最后一点:清单是二进制的一部分。远程更新改不了它。必须重新构建并重新提交。
自带清单的第三方 SDK
大多数较新的库都自带清单。使用这些 API 的 Expo 包在各自的 ios 目录里都有 PrivacyInfo.xcprivacy,React Native 也一样。问题出在它们与你 app 的链接方式上。
Expo 文档提醒:对于静态链接的 CocoaPods 依赖(Expo 包就是这种情况),Apple 并不能正确读取所有清单。所以你可能需要把它们的理由复制到你 app 的配置里。
为了减少这项工作,expo-build-properties 的 ios.privacyManifestAggregationEnabled 设置会把 CocoaPods 依赖的清单合并成一个文件。在 Expo 为我的项目(SDK 57)生成的 Podfile 中,这项合并默认是开启的,除非你手动关掉。
合并有个局限:它只能看到自带清单的依赖。没有清单的老库依然是看不见的。所以要看清邮件里提到的文件路径:它告诉你该在哪里修。
| 被指出的文件 | 在哪里修 |
|---|---|
| app 的可执行文件 | ios.privacyManifests |
某个 .framework | 更新该 SDK |
某个扩展 .appex | 扩展自己的清单 |
提交前检查
Expo 的建议是提交后等 Apple 的邮件。这样也行,但每来回一次就要多花一次构建。下面是我提交前做的四项检查。
**1. 列出你的依赖声明了什么。**在项目根目录执行:
find node_modules -name PrivacyInfo.xcprivacy -not -path '*/example/*' \
-exec grep -ho "NSPrivacyAccessedAPICategory[A-Za-z]*" {} + | sort | uniq -c
2026 年 10 月 15 日在 Solys 上,这条命令找到了 13 份清单和四个类别:文件时间戳、偏好设置、开机后经过的时间和磁盘空间。列出的每个类别都必须出现在 app 的最终清单里,或者出现在使用它的 framework 的清单里。
**2. 搜索你自己的原生代码。**自写模块、插件、小组件之类的 target:这部分代码,没有哪个 SDK 会替你声明。
grep -rlE "UserDefaults|systemUptime|mach_absolute_time|creationDate|modificationDate|volumeAvailableCapacity|activeInputModes" \
--include='*.swift' --include='*.m' --include='*.mm' modules plugins targets
按你的项目调整目录。在 Solys 上,正是这次搜索发现了通过 App Group 共享的偏好设置,所以才有了 1C8F.1。
**3. 查看生成的清单。**运行 npx expo prebuild -p ios,然后打开 ios/<YourAppName>/PrivacyInfo.xcprivacy。注意:这条命令可能会删除并重建 ios 目录。如果你在里面手动改过东西,请在项目副本上操作。
**4. 检查最终构建。**这是唯一算数的证据。从 expo.dev 上该构建的页面下载 IPA,然后在 Mac 上运行:
unzip -q MyApp.ipa -d ipa
find ipa/Payload -name PrivacyInfo.xcprivacy
plutil -p ipa/Payload/*.app/PrivacyInfo.xcprivacy
第二行列出所有内嵌的清单,包括 app 的和各 framework 的。第三行显示 app 的清单,拿它和第 1 步的列表对比。
清单要和你的商店页面说法一致
清单不是孤立存在的。如果它声明了跟踪或收集的数据,你的 App Store 页面和隐私政策也必须说同样的话。三者之间不一致,之后可能招来人工拒绝,依据是 guideline 5.1.1。需要对齐哪些内容,详见应用的隐私政策。
提交构建,不留意外
ITMS 错误只要会看,修起来很快。之后出现的拒绝则需要多想一步:处理方法见应用被 Apple 拒绝这一页,最常见的原因见Apple 为什么拒绝你的 app。
想在提交前检查你的材料,Apple Review Kit 汇集了我的检查清单和能审阅你项目的 Claude Code skills。如果提交卡住了,想找人帮你看看,预约和我一对一一小时:我们视频连线,在你的屏幕上一起过你的项目。如果你正在准备第一次上架,做一下免费诊断:它会帮你把步骤理顺,并把我的 Apple 防拒审清单发给你。
常见问题
ITMS-91053 会阻止我提交构建吗?
Apple 写明,自 2024 年 5 月 1 日起,App Store Connect 不再接受没有说明这些 API 用途的 app。不过也有开发者在构建被接受的情况下收到这封邮件,只当作警告。无论哪种情况,都要在下次构建前修好清单。
UserDefaults 该选哪个理由代码?
如果 app 只读写自己的偏好设置,选 CA92.1。如果通过 App Group 与小组件或扩展共享,再加上 1C8F.1。C56D.1 是第三方 SDK 专用的:别把它放进你 app 的清单。
需要把每个 SDK 的清单都复制到 app.json 里吗?
不一定。如果开启了清单合并,CocoaPods 依赖的清单会在安装 pods 时合并在一起。只有当某个类别在最终清单里缺失时才去复制它,并配上描述真实用途的理由。
我的小组件或扩展需要自己的清单吗?
根据 Apple 的说法,每个包含使用这些 API 的可执行文件的 bundle,都必须有自己的清单。扩展是独立的可执行文件,位于自己的 bundle 中:app 的清单覆盖不了它。
不重新构建能修复 ITMS-91053 吗?
不能。隐私清单是二进制的一部分。改好配置,重新构建,然后先在 TestFlight 里测试,再提交审核。