你点了扫描小票,App 卡了一帧,然后消失了。没崩溃弹窗、没错误提示,直接回到主屏。把设备连上 Mac,打开 Console.app,你会看到这样一行:“This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app’s Info.plist must contain an NSCameraUsageDescription key with a string value explaining to the user how the app uses this data.” iOS 在这种情况下不抛可恢复的异常;它由 TCC(Transparency, Consent, and Control)层直接用 SIGABRT 终结进程。
最快的修法: 那行 console 已经写明你缺的是哪个 key。把它加进 Info.plist(现代 Xcode 上则是 INFOPLIST_KEY_* build setting),配一句面向用户的描述,重新构建即可。整个修复就一行字符串。真正要做的功夫是三件事:(1)从 console 读对 key,(2)写一段能通过 App Review 的描述,(3)确认这个 key 真的进了归档好的 build —— 因为这个 bug 最痛苦的版本,就是源码里有 key、却在 TestFlight build 里被剥掉了。
两种”隐私”被拒经常被搞混。缺用途描述会让 App 在运行时崩溃(本文讲的就是这个)。而缺失或不完整的
PrivacyInfo.xcprivacymanifest 不会让任何东西崩溃;它会在 App Store Connect 阶段卡住上传,并发来一封关于”required reason API”的邮件。如果你的 App 跑得好好的、只是上传被拒,看文末的隐私清单一节。
我缺的是哪个 key?
console 那行会直接写出 key 名,所以从它入手。如果你暂时拿不到 console,就用你点的功能反查它要求的 key(截至 2026 年 6 月,已对照 Apple 的 Information Property List 参考核对):
| 崩溃的功能 | 需要的 Info.plist key |
|---|---|
相机拍摄 / AVCaptureDevice | NSCameraUsageDescription |
| 麦克风 / 录音 | NSMicrophoneUsageDescription |
| 读取相册 | NSPhotoLibraryUsageDescription |
| 仅写入相册 | NSPhotoLibraryAddUsageDescription |
| 使用期间定位 | NSLocationWhenInUseUsageDescription |
| 始终 / 后台定位 | NSLocationWhenInUseUsageDescription 和 NSLocationAlwaysAndWhenInUseUsageDescription |
| IDFA / 广告归因(ATT) | NSUserTrackingUsageDescription |
| 蓝牙(central 或 peripheral) | NSBluetoothAlwaysUsageDescription |
| 本地网络(Bonjour、socket) | NSLocalNetworkUsageDescription(Bonjour 还要 NSBonjourServices) |
| 通讯录 | NSContactsUsageDescription |
| 日历 / 提醒 | NSCalendarsUsageDescription / NSRemindersUsageDescription |
| HealthKit 读 / 写 | NSHealthShareUsageDescription / NSHealthUpdateUsageDescription |
| HomeKit | NSHomeKitUsageDescription |
| 运动 / 计步 | NSMotionUsageDescription |
| 语音识别 | NSSpeechRecognitionUsageDescription |
| Face ID | NSFaceIDUsageDescription |
常见原因
按最常踩到的 API 面排序。
1. 没配对应 key 就访问相机 / 麦克风 / 相册
调用 AVCaptureDevice.requestAccess(for: .video)、PHPhotoLibrary.requestAuthorization、或用 .camera source type 实例化 UIImagePickerController,而没有 NSCameraUsageDescription、NSMicrophoneUsageDescription 或 NSPhotoLibraryUsageDescription,会被立刻终结进程。
如何识别:崩溃栈里能看到 TCC(Transparency, Consent, Control)framework;console 行明确写出缺哪个 key。
2. 没配 NSLocationWhenInUseUsageDescription 就用定位
CLLocationManager.requestWhenInUseAuthorization() 需要 NSLocationWhenInUseUsageDescription。如果同时调用了 requestAlwaysAuthorization(),iOS 11+ 还要求两个 key 都在:NSLocationWhenInUseUsageDescription 和 NSLocationAlwaysAndWhenInUseUsageDescription。
如何识别:Console:“Trying to start MapKit location updates without prompting for location authorization.” 或者首次 locationManager.startUpdatingLocation() 即崩。
3. iOS 14+ 用 IDFA 但没配 NSUserTrackingUsageDescription
ATTrackingManager.requestTrackingAuthorization 需要 NSUserTrackingUsageDescription。没有这个 key 调用会挂住或静默返回 .denied;某些 SDK(Facebook、Branch)在 init 时甚至会硬崩。
如何识别:AppDelegate 调用 Settings.shared.advertiserTrackingEnabled = true 之类 SDK init 代码时首次启动就崩。
4. 没配 NSBluetoothAlwaysUsageDescription 就用蓝牙
实例化 CBCentralManager 或 CBPeripheralManager 需要 NSBluetoothAlwaysUsageDescription(iOS 13+)。老的 NSBluetoothPeripheralUsageDescription 已弃用;iOS 13+ 上只设老 key 还是崩。
如何识别:崩溃栈带 CoreBluetooth,console 写出 key 名。
5. iOS 14+ 本地网络访问
任何稍微复杂点的 socket 调用(NWConnection、multipeer、Bonjour 浏览)需要 NSLocalNetworkUsageDescription。iOS 13 上一切正常的代码到 iOS 14 上要么崩、要么连接静默超时。
如何识别:iOS 13 模拟器上正常,iOS 14+ 真机上会弹*“App 想要查找并连接局域网内的设备”*;如果 Info.plist 缺 key,提示根本不出现,连接静默超时。
6. 通讯录 / 日历 / 提醒 / Health / HomeKit / Motion
每个受保护资源都有自己的 key:NSContactsUsageDescription、NSCalendarsUsageDescription、NSRemindersUsageDescription、NSHealthShareUsageDescription、NSHealthUpdateUsageDescription、NSHomeKitUsageDescription、NSMotionUsageDescription。崩溃栈在 console 里永远会指出缺哪一个。
如何识别:Console:“This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app’s Info.plist must contain an <KEY>…“
7. 源码里有但归档里被剥掉
最痛苦的一种:项目能编译、模拟器能跑,TestFlight build 一启动就崩。Build settings(自定义 Info.plist 路径、条件构建、重新生成 Info.plist 的脚本)在归档时把 key 剥走了。
如何识别:本地 Debug 正常;只有 Release / Distribution 归档崩。从 .ipa 中提取 Info.plist 看不到那个 key。
开始之前
- 在真机上复现崩溃(不只是模拟器)—— 某些受保护 API 在模拟器上返回 mock 数据,永远不崩。
- 把崩溃日志做 symbolicate,或者直接看实时 Console.app —— iOS 打出的那行错误明确告诉你缺哪个 key。
- 看看问题是否只在 Release build 出现;如果是,看 build settings 而不是源码。
需要收集的信息
- Console.app 中那条*“This app has crashed because…”*的完整原文。
- 每个 target 的 Info.plist 跑
grep -E "NS[A-Z][A-Za-z]+UsageDescription" Info.plist的输出。 - 触发崩溃的具体 API 调用(相机、定位、ATT、蓝牙等)。
- 启动时初始化的所有 SDK —— 很多 SDK 在 init 里就请求权限。
- Build settings:
INFOPLIST_FILE、INFOPLIST_PREPROCESS、任何动 Info.plist 的自定义 build phase。
修复步骤
步骤 1:从 console 读出确切的缺失 key
设备插上 Mac,打开 Console.app,按 App 名过滤,复现崩溃。那行非常明确:
This app has crashed because it attempted to access privacy-sensitive data
without a usage description. The app's Info.plist must contain an
NSCameraUsageDescription key with a string value explaining to the user
how the app uses this data.
把它当权威 —— 准确指出该加哪个 key。
步骤 2:加上 key,写一段面向用户、具体的描述
打开 App target 的 Info.plist(或现代 Xcode 中的 INFOPLIST_KEY_* build settings)。加上:
<key>NSCameraUsageDescription</key>
<string>Scan receipts and barcodes to add items to your expense list.</string>
App Review 对描述的硬要求:
- 必须具体(不能只写”用于相机访问”—— 要说明对用户的好处)。
- 必须用用户本地语言;为每种支持的语言提供
InfoPlist.strings。 - 不要写”可能”/“也许”—— 描述实际用途。
- 必须与权限弹出当下 App 真的在做的事一致。
步骤 3:确认 key 真的进了归档
这一步大多数团队会跳过,而”本地正常、TestFlight 崩溃”的 bug 就藏在这里。归档里那份编译后的 Info.plist 才是唯一权威。(注意:你在旧帖里看到的 xcrun PackageApplication 命令早在 Xcode 8.3 就被移除了,别再用。)
如果你导出了 .ipa,直接读它的 Info.plist:
unzip -p YourApp.ipa "Payload/*.app/Info.plist" \
| plutil -p - \
| grep UsageDescription
如果你手里只有 .xcarchive,让 plutil 直接指向归档里那份编译好的 plist(无需导出):
plutil -p \
~/Library/Developer/Xcode/Archives/*/YourApp\ */Products/Applications/YourApp.app/Info.plist \
| grep UsageDescription
或者手动来:在 Xcode 的 Organizer 里右键归档,选 Show in Finder,对 .xcarchive 选 显示包内容,打开 Products/Applications/YourApp.app/Info.plist。如果 key 在源码里有、这里却没有,说明是你的 build settings 或某个 build phase 脚本把它剥掉了 —— 去修那里,而不是改源码。
步骤 4:项目使用现代 Xcode(没有 Info.plist 文件)的情况
从 Xcode 13/14 起,新项目默认带 GENERATE_INFOPLIST_FILE = YES、不再有独立的 Info.plist 文件。截至 2026 年 6 月,当前 Xcode(Xcode 26)仍然如此。每个用途 key 对应一条以 INFOPLIST_KEY_ 为前缀的 build setting。在 Build Settings 里(搜 key 名)加,或粘进 .xcconfig:
INFOPLIST_KEY_NSCameraUsageDescription = Scan receipts and barcodes to add items to your expense list.
更省事的做法:打开 target 的 Info tab,点 +,加一行 “Privacy - Camera Usage Description”。两种方式 build 系统都会在编译期合成 Info.plist,所以用步骤 3 的归档检查正好能确认它落进去了。
步骤 5:为每种支持的语言做本地化
Info.plist 中英文字符串是 fallback。每个 locale 加一份 InfoPlist.strings:
// en.lproj/InfoPlist.strings
"NSCameraUsageDescription" = "Scan receipts and barcodes to add items to your expense list.";
// zh-Hans.lproj/InfoPlist.strings
"NSCameraUsageDescription" = "扫描小票和条码,将物品添加到你的支出列表。";
// es.lproj/InfoPlist.strings
"NSCameraUsageDescription" = "Escanea recibos y códigos de barras para agregar artículos a tu lista de gastos.";
没有本地化字符串的话,App Review 可能以”非英语用户在系统权限弹窗中看到英文”为由拒掉。
步骤 6:审计每个 SDK 暗藏的权限请求
SDK 在 init 时会请求权限,所以一个你”没用到”的 key 也可能是必需的:
nm YourApp.app/YourApp | grep -E "requestAuthorization|requestAccess|requestTracking"
或者查看你依赖的 SDK 的 release notes。常见嫌疑:Facebook SDK(ATT)、Branch(ATT、定位)、Firebase Messaging(通知)、Adjust(ATT)、音频/语音类 SDK(麦克风)。SDK 碰过的每一项权限都需要在你的 Info.plist 里有用途描述,哪怕你自己代码完全没碰那个 API。
顺手在 SDK 清单里也查一下另一个相关但不同的”隐私清单”要求(见下一节)—— 它不会让 App 崩,但会卡住上传。
步骤 7:在 CI 加一个缺 key 检查
5 行脚本就能防止这个回归:
REQUIRED_KEYS="NSCameraUsageDescription NSPhotoLibraryUsageDescription NSLocationWhenInUseUsageDescription"
for key in $REQUIRED_KEYS; do
/usr/libexec/PlistBuddy -c "Print :$key" "$APP_PATH/Info.plist" \
|| { echo "Missing $key"; exit 1; }
done
跑在归档好的 .app 上,而不是源码 Info.plist 上,这样 build settings 漂移也能抓到。
验证
- 真机冷启动,触发原本崩溃的功能;系统权限弹窗带着你写的描述出现。
- 拒绝权限,App 优雅处理(不崩、给出 fallback UI)。
- 切到另一种语言再启动,描述文案是那种语言。
- 昨天 TestFlight build 的崩溃日志在新 build 上不再复现。
- 归档 build 通过 CI 检查。
别搞混:隐私清单(PrivacyInfo.xcprivacy)
如果你的 App 跑得好好的、只是上传被拒,那你撞上的是另一条规则,加用途描述不解决问题。从 2024 年 5 月 1 日起,凡是调用了”required reason API”(例如 UserDefaults、文件时间戳、磁盘空间、systemUptime)却没在 PrivacyInfo.xcprivacy 隐私清单文件里声明的上传,会被 App Store Connect 拒。被拒是一封来自 App Store Connect、点名 API 类别(NSPrivacyAccessedAPICategoryUserDefaults 之类)的邮件,而不是运行时崩溃。
两点关键区别:
- 用途描述是运行时关卡。缺它,API 一运行 App 就崩。
- 隐私清单是提交关卡。缺它,上传在 App Store Connect 被卡,但 App 在开发中照样能跑。
很多第三方 SDK(上传时你会看到 Apple 的”常用 SDK”清单被标出)必须自带一份签名的 PrivacyInfo.xcprivacy。如果你的上传是因为某个 SDK 被卡,把那个 SDK 升到自带 manifest 的版本。确切的类别字符串和被认可的理由,见 Apple 的 privacy manifest files 和 describing use of required reason API 文档。
长期预防
- 为每个 target 维护一份”用途 key 清单”,列出代码路径需要哪些;每次发版前过一遍。
- 把新增 SDK 当作触发”用途描述审查”的事件;SDK 文档总会列出涉及哪些权限。
- 每个 shipped 语言都本地化每一个用途描述;CI 步骤检测
InfoPlist.strings是否缺 key。 - DEBUG 下在 App 启动时加一个运行时检查,断言每个 key 存在;缺失时直接 fatal 在开发期暴露。
- 把归档的 Info.plist 作为构建产物保留;跨版本对比,发现静默丢失。
- 复杂 App 写集成测试,每次在干净模拟器(无任何已授权)上走一遍受权限保护的功能;缺 key 立即暴露。
常见坑
- 在系统权限弹窗预览里改字符串 —— 那是运行时显示,不是真正的来源。要改源码 Info.plist。
- iOS 11+ 单独用
NSLocationAlwaysUsageDescription;Always 权限需要NSLocationWhenInUseUsageDescription加NSLocationAlwaysAndWhenInUseUsageDescription两个。 - 写”We need camera access.”这种空洞描述。App Review 会拒 —— 要具体。
- iOS App target 和 Watch target 都调用某 API,但只在其中一个 target 设了 key。
- 重构时删了 key 没注意以前触发它的 SDK 还在 —— 下个版本崩溃回归。
- 以为模拟器能抓到这种问题 —— 模拟器对很多 API 返回 mock 数据,永远触发不到缺 key 崩溃。
FAQ
Q:Console 说要 NSCameraUsageDescription,但我没用相机,为什么?
某个打包进来的 SDK 在用。跑 grep -RE "AVCapture|UIImagePicker|requestAccess.*video" Pods/ 找出来。要么给那个 SDK 加描述,要么如果你不需要那个功能就把 SDK 删了。
Q:描述字符串能写空吗?
不能。App Review 会拒掉空字符串或一个词的描述。苹果要求至少一句话说明 App 怎么用这份数据。
Q:我 Watch App 崩了,但我已经在手机 App 的 Info.plist 里加了 key。
Watch App 有自己的 Info.plist。Watch target 的 Info.plist 也要加。App Clips、Widgets、以及任何独立初始化 CLLocationManager 的 framework 同理。
Q:为什么模拟器不崩?
模拟器对部分 API 的 TCC 强制是宽松的;比如 AVCaptureDevice.requestAccess 在模拟器上可能不查 Info.plist 直接返回。一定要在真机上测。设备测试本身有问题时,参考TestFlight Build 在设备上看不到。
Q:App Review 说”用途说明太含糊”,怎样算合格?
审核员希望你说出具体功能:“To scan receipts for expense tracking,” 而不是 “For camera access.”。要引用 App 中使用这份数据的具体功能。
Q:App 跑得好好的,但 App Store Connect 因隐私拒了上传,是同一个 bug 吗?
不是。缺用途描述是运行时崩溃;上传因”required reason API”被拒,是另一条从 2024 年 5 月 1 日起生效的 PrivacyInfo.xcprivacy 隐私清单规则。那封邮件会点名 NSPrivacyAccessedAPICategoryUserDefaults 之类的 API 类别。要加或更新隐私清单(往往是把出问题的 SDK 升个版本),而不是动用途描述。
Q:我只加了相机 key,但 console 现在还要麦克风?
UIImagePickerController 和 AVCaptureSession 录视频会带上音频,所以系统会同时检查 NSCameraUsageDescription 和 NSMicrophoneUsageDescription。录视频时两个 key 都要加。
相关阅读
标签: #排查 #ios #info-plist #privacy #crash