App 启动即崩溃:缺失 NSXxxUsageDescription 修复

iOS 在隐私敏感 API 运行而 Info.plist 缺少对应用途描述的那一刻直接终结 App。从设备 console 读出缺失的 key、补上、确认它进了归档,再发版。

你点了扫描小票,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.xcprivacy manifest 不会让任何东西崩溃;它会在 App Store Connect 阶段卡住上传,并发来一封关于”required reason API”的邮件。如果你的 App 跑得好好的、只是上传被拒,看文末的隐私清单一节。

我缺的是哪个 key?

console 那行会直接写出 key 名,所以从它入手。如果你暂时拿不到 console,就用你点的功能反查它要求的 key(截至 2026 年 6 月,已对照 Apple 的 Information Property List 参考核对):

崩溃的功能需要的 Info.plist key
相机拍摄 / AVCaptureDeviceNSCameraUsageDescription
麦克风 / 录音NSMicrophoneUsageDescription
读取相册NSPhotoLibraryUsageDescription
仅写入相册NSPhotoLibraryAddUsageDescription
使用期间定位NSLocationWhenInUseUsageDescription
始终 / 后台定位NSLocationWhenInUseUsageDescription NSLocationAlwaysAndWhenInUseUsageDescription
IDFA / 广告归因(ATT)NSUserTrackingUsageDescription
蓝牙(central 或 peripheral)NSBluetoothAlwaysUsageDescription
本地网络(Bonjour、socket)NSLocalNetworkUsageDescription(Bonjour 还要 NSBonjourServices
通讯录NSContactsUsageDescription
日历 / 提醒NSCalendarsUsageDescription / NSRemindersUsageDescription
HealthKit 读 / 写NSHealthShareUsageDescription / NSHealthUpdateUsageDescription
HomeKitNSHomeKitUsageDescription
运动 / 计步NSMotionUsageDescription
语音识别NSSpeechRecognitionUsageDescription
Face IDNSFaceIDUsageDescription

常见原因

按最常踩到的 API 面排序。

1. 没配对应 key 就访问相机 / 麦克风 / 相册

调用 AVCaptureDevice.requestAccess(for: .video)PHPhotoLibrary.requestAuthorization、或用 .camera source type 实例化 UIImagePickerController,而没有 NSCameraUsageDescriptionNSMicrophoneUsageDescriptionNSPhotoLibraryUsageDescription,会被立刻终结进程。

如何识别:崩溃栈里能看到 TCC(Transparency, Consent, Control)framework;console 行明确写出缺哪个 key。

2. 没配 NSLocationWhenInUseUsageDescription 就用定位

CLLocationManager.requestWhenInUseAuthorization() 需要 NSLocationWhenInUseUsageDescription。如果同时调用了 requestAlwaysAuthorization(),iOS 11+ 还要求两个 key 都在:NSLocationWhenInUseUsageDescriptionNSLocationAlwaysAndWhenInUseUsageDescription

如何识别: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 就用蓝牙

实例化 CBCentralManagerCBPeripheralManager 需要 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:NSContactsUsageDescriptionNSCalendarsUsageDescriptionNSRemindersUsageDescriptionNSHealthShareUsageDescriptionNSHealthUpdateUsageDescriptionNSHomeKitUsageDescriptionNSMotionUsageDescription。崩溃栈在 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_FILEINFOPLIST_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 filesdescribing 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 权限需要 NSLocationWhenInUseUsageDescriptionNSLocationAlwaysAndWhenInUseUsageDescription 两个。
  • 写”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 现在还要麦克风?

UIImagePickerControllerAVCaptureSession 录视频会带上音频,所以系统会同时检查 NSCameraUsageDescriptionNSMicrophoneUsageDescription。录视频时两个 key 都要加。

相关阅读

标签: #排查 #ios #info-plist #privacy #crash