文章

iOS 权限系统详解:从 Info.plist 到弹窗交互

iOS 权限以用户隐私为核心:Info.plist 写清用途描述、系统弹窗询问、设置里可随时关闭,跨端开发者必须按需申请、优雅降级,否则审核被拒或功能静默失效。

iOS 权限系统详解:从 Info.plist 到弹窗交互

一句话概括

iOS 权限体系的灵魂就一句话:用到敏感能力前,必须在 Info.plist 写清「为什么要用」,系统才弹窗问用户,且用户拒绝后你没法再弹第二次。和 Android「请求即授权、还能每次再问」完全不同,iOS 把「知情权」和「随时撤销权」牢牢交还给用户。

对跨端开发者,最坑的不是代码怎么写,而是:权限描述漏配 → 调用 API 时系统静默失败、连弹窗都没有;或者启动就一次性申请所有权限 → 用户一上来就全拒、审核还可能被毙。理解这套机制,才能在接入摄像头、定位、推送时一次配对。

核心知识点

1. Info.plist 的 UsageDescription:权限的「入场券」

每个敏感能力都有对应的 NSxxxUsageDescription 键,写在 Info.plist 里。系统只在这个键存在时才会弹窗;没有它,调用 API 直接失败且无任何提示。最常用的几个:

能力Info.plist Key典型场景
摄像头NSCameraUsageDescription扫码、拍照
麦克风NSMicrophoneUsageDescription录音、视频通话
相册(读写)NSPhotoLibraryUsageDescription选照片
相册(仅写)NSPhotoLibraryAddUsageDescription存图不读
使用期间定位NSLocationWhenInUseUsageDescription前台导航
始终定位NSLocationAlwaysAndWhenInUseUsageDescription后台轨迹(iOS 11+)
通知无 plist 键,走 UNUserNotificationCenter推送
跟踪(IDFA)NSUserTrackingUsageDescription广告归因(ATT)

关键规则:哪怕你自己的代码没调系统 API,只要集成的第三方 SDK 内部调了,也得在 Info.plist 配对应描述——否则 SDK 触发请求时系统因缺描述而静默失败。这是跨端项目最高频的「功能莫名其妙不工作」元凶。

1
2
// ❌ 只配了 NSCameraUsageDescription,但 image_picker 内部还读相册 → 选图静默失败
// ✅ 摄像头、相册读、麦克风按实际用到的能力一次性配齐

2. 弹窗触发时机:首次调用 API 才弹,拒了就再也弹不了

iOS 和 Android 最大的不同在这里:

1
2
3
4
第一次调用 camera API → 系统弹 NSCameraUsageDescription 弹窗
用户选择 → 结果存进系统权限库
第二次调用 → 不再弹窗,直接返回之前的结果
用户点了「不允许」 → 之后所有请求直接返回 denied,绝不再弹

也就是说:用户拒绝后,你的 App 没有任何 API 能再次弹窗。唯一出路是引导用户去「系统设置 → 你的 App」手动开。所以描述文字要写清楚真实用途,别糊弄。

3. 定位权限:When In Use vs Always + iOS 14 精确位置

定位是权限里最绕的一块。层次是 不允许 → When In Use(前台)→ Always(前后台),后者需要先在 Signing & Capabilities 开 Background Modes 的 Location updates:

1
2
3
4
5
6
7
8
let manager = CLLocationManager()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest

// 先要前台定位
manager.requestWhenInUseAuthorization()
// 之后真需要后台时再要(不是一上来就 Always)
manager.requestAlwaysAuthorization()

iOS 14 起多了一个精确位置开关:即使用户授权了定位,也可能只给你 1–2 公里模糊坐标。需要精确位置时要用 requestTemporaryFullAccuracyAuthorization(withPurposeKey:) 临时申请,并在 Info.plist 配 NSLocationTemporaryUsageDescriptionDictionary。真机定位「偏差巨大」先别怀疑代码,去设置里看「精确位置」开关是不是被关了。

4. 通知权限:授权与注册是两步

通知走 UserNotifications 框架,注意请求授权和向 APNs 注册 token 是两件事,顺序不能反:

1
2
3
4
5
6
7
8
9
10
import UserNotifications

UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, _ in
    if granted {
        // 用户同意后才注册远程推送,拿到 deviceToken
        DispatchQueue.main.async {
            UIApplication.shared.registerForRemoteNotifications()
        }
    }
}

收不到 token 的常见原因:Entitlements 没开 Push Notifications、没配后台模式、或网络连不上 APNs。注意:didRegisterForRemoteNotificationsWithDeviceToken 的回调和「用户是否授权通知显示(横幅/声音/角标)」无关——只要 App 开启了 Push 能力且设备连得上 APNs,哪怕用户拒绝授权也会返回 token(静默推送照样能送达);用户授权只决定能不能弹横幅,不影响 token 获取。

5. iOS 14+ 新权限 与 iOS 17 隐私清单(重点更新)

  • 本地网络 NSLocalNetworkUsageDescription:访问局域网设备(BLE / mDNS / 打印)时弹窗,常配 NSBonjourServices
  • 粘贴板:iOS 14 读取会顶部横幅提示,iOS 16+ 读前需授权,别在启动时偷读
  • ATT NSUserTrackingUsageDescription:要 IDFA 必须弹,且要在 applicationDidBecomeActive 之后弹,否则上架会被拒
  • 隐私清单 PrivacyInfo.xcprivacy(iOS 17 起强制):自 2024-05-01 起,提审 App(及列出的第三方 SDK 如 Firebase、SDWebImage 等)必须带隐私清单,声明「收集了哪些数据」「用了哪些需说明理由的 API(如 UserDefaults、文件时间戳)」。漏了会收到 ITMS-91053 警告甚至被拒——这是原文没有、但如今面试和实际上架都绕不开的新知识点。

其实你每天都在用

  • image_picker 选照片黑屏/没反应:九成是 Info.plist 漏了 NSPhotoLibraryUsageDescription,系统静默失败
  • 后台导航没轨迹:没在 Signing & Capabilities 开 Location updates 后台模式,也没申请 Always 授权
  • 推送收不到 token:Entitlements 没开 Push Notifications,或没先 requestAuthorization 就 registerForRemoteNotifications
  • 广告 SDK 要 IDFA 被拒:没写 NSUserTrackingUsageDescription,或 ATT 弹窗时机不对
  • 真机定位偏了几公里:iOS 14+ 的「精确位置」开关被用户关了,不是你代码的问题
  • permission_handler 里 isPermanentlyDenied:用户之前点过「不允许」,只能引导去系统设置开
  • 上架收到 ITMS-91053:缺隐私清单,第三方 SDK 用了需说明理由的 API 没声明
  • 启动时弹一堆权限请求:用户一上来全拒,体验崩、审核也可能被毙——应该「用到再要」

常见误解(FAQ)

❌ 误区一:「用户拒绝权限后,我换个时机再调一次 API 就能重新弹窗」

不能。iOS 一旦用户选了「不允许」,后续所有请求直接返回 denied,系统绝不再弹。正确做法是检测 denied / permanentlyDenied,弹对话框引导用户去「系统设置 → 你的 App」手动开启。

❌ 误区二:「只有我自己代码调了系统 API,才需要在 Info.plist 配描述」

错。第三方 SDK 或原生插件内部调用了摄像头 / 定位 / 相册,同样触发权限请求,而系统只看 Info.plist 有没有对应 UsageDescription。漏配的结果不是崩溃,而是静默失败——功能没反应、还没报错,最难查。

❌ 误区三:「App 启动时就一次性把相机、定位、通讯录全申请了,省事」

这是审核被拒和用户体验翻车的高频原因。Apple 建议按需、逐个申请,且描述要具体真实。一次性全要,用户往往直接全拒,你反而啥都拿不到。

❌ 误区四:「NSLocationAlwaysUsageDescription 还在用,配它就够了」

已废弃。iOS 11+ 用 NSLocationAlwaysAndWhenInUseUsageDescription,前者只在前台场景会被忽略。而且 Always 权限审核更严,不是真有后台定位需求别申请。

❌ 误区五:「权限描述随便写两句就能过审」

审核会看描述是否「真实、具体、和你的功能匹配」。写「用于优化广告体验」却没有个性化广告功能、写「拍照」但 App 里压根没拍照,都会被拒。好描述是「扫描商品条码以快速加入购物车」这种具体到动作的句子。

一句话总结

iOS 权限的本质是把知情权和撤销权交给用户:Info.plist 写清用途是入场券、系统弹窗只问一次、拒绝后只能引导去设置——所以「按需申请、描述写真、被拒优雅降级」不是政治正确,而是你的 App 能不能过审、用户愿不愿意给权限的硬道理,而 iOS 17 起的隐私清单则把这个逻辑从「代码层」又往前推到了「提审层」。

本文由作者按照 CC BY 4.0 进行授权