Highlight
Apple 隐私工程师 Joey Tyson 把 app 开发流程拆成规划、设计、开发、测试、部署五个阶段,逐一给出对应的隐私工程动作和系统 API。
核心内容
很多开发者在功能上线前一周才开始想隐私问题:要弹几个权限框、隐私营养标签怎么填、第三方 SDK 有没有合规风险。这种”事后补”的姿态会让你被迫做妥协——架构已经定型,数据流已经跨越了多个上下文,再想做端到端加密或设备端处理都已经动不了大手术。Joey Tyson 在这个 session 里给出的核心主张就是:隐私和安全、本地化一样,没法在后期 retrofit,必须从第一行代码之前就介入。
他把 app 开发拆成五个阶段——planning、design、development、testing、deployment——每个阶段都给出具体的隐私动作。规划阶段产出”隐私保证声明”(privacy assurance),用人话写清楚 app 会怎么处理用户数据;设计阶段把这些保证翻译成可感知的 UI(首启页摘要、状态变更提示、上下文化的选择);开发阶段用 PhotosPicker、Location Button、CloudKit encryptedValues、PIR、AdAttributionKit 这些系统能力把保证落到代码里;测试阶段写 unit / integration / UI test 验证保证没被破坏;部署阶段填 Privacy Nutrition Label,归档时用 Xcode 的 “Generate Privacy Report” 汇总所有 privacy manifest。这套流程的好处是:每个工程决策都能追溯到一条最初的隐私保证,避免”为什么我们要把位置上传服务器”这种问题在 review 时才被提出。
主讲人还反复强调一个反直觉的点:减少权限弹窗本身就是隐私设计。系统级 picker(PhotosPicker、ContactPicker、Location Button)由 system process 渲染,用户的选择已经隐含同意,不需要再弹框;这比”先申请整个相册权限再让用户选”的体验和隐私性都更好。
详细内容
规划:用四大支柱写出隐私保证(03:33)
Apple 内部用四个 pillar 引导写隐私保证:data minimization、on-device processing、transparency and control、security protections。主讲人以一个虚拟 app “Pal About” 为例,逐条示范:
- 数据最小化 → “We only retain aggregate usage data.”
- 强默认 → “When searching for nearby places, your current location is not stored by default.”
- 设备端处理 → “Suggested meetup locations are only generated locally using on-device data.”
- 透明与控制 → “Photos you upload are only used to train generative models if you opt in to improve intelligence features.”
- 安全保护 → “We cannot read messages to your friends in transit between devices.”(用 end-to-end encryption 在架构上兜底)
这些声明把营销语言落到了工程需求上:每一条都对应一个具体的技术选型。
开发:用系统 picker 取代权限弹窗(10:29)
PhotosPicker 在 system process 中渲染,app 只拿到用户实际选中的那几张照片,不需要请求相册权限:
// Define the app's Photos picker
PhotosPicker(
selection: $viewModel.selection,
matching: .images,
preferredItemEncoding: .current,
photoLibrary: .shared()
) {
Text("Select Photos")
}
// Configure a half-height Photos picker
.photosPickerStyle(.inline)
.ignoresSafeArea()
.frame(height: 340)
关键点:
selection: $viewModel.selection绑定一个状态,只有用户主动勾选的照片才会回流到 app。matching: .images限定媒体类型,picker 不会展示视频。photoLibrary: .shared()使用系统共享的相册句柄,整个选择流程跑在系统进程里,app 不直接访问相册。.photosPickerStyle(.inline)把 picker 嵌入当前 UI 而不是弹出全屏,配合.frame(height: 340)做半高展示。- 因为不调用相册权限 API,App Tracking 之外不会出现多余的权限弹窗。
开发:Location Button 一次性位置共享(11:33)
LocationButton(LocationButton.Title.currentLocation) {
// Start updating location when user taps the button.
// Location button doesn't require the additional
// step of calling 'requestWhenInUseAuthorization()'.
manager.startUpdatingLocation()
}.foregroundColor(Color.white)
.cornerRadius(27)
.frame(width: 210, height: 54)
.padding(.bottom, 30)
关键点:
LocationButton(LocationButton.Title.currentLocation)使用系统标题文案,系统会校验”按钮真的是被用户点击触发”,防止 app 伪造点击。- 闭包里直接调用
manager.startUpdatingLocation(),无需先调requestWhenInUseAuthorization(),省掉一次权限弹窗。 - 第一次点击时系统会弹一次确认框解释按钮作用;之后再点击不再弹框,直接共享当前位置。
- 屏幕顶部仍会出现位置指示器,用户始终知道位置正在被使用。
- 样式(颜色、圆角、尺寸)可以自定义,匹配 app 视觉风格。
开发:CloudKit 字段级加密(13:48)
myRecord.encryptedValues["encryptedStringField"] = "Sensitive value"
let decryptedString = myRecord.encryptedValues["encryptedStringField"] as? String
关键点:
encryptedValues是CKRecord上的字典属性,赋值时由系统自动加密、读取时自动解密,开发者不需要管密钥。- 配套要求是在 CloudKit schema 里把字段类型设为
EncryptedString、EncryptedBytes等加密变体;CKAsset默认就是加密的。 - 用户启用 Advanced Data Protection 时,这些字段自动升级为 end-to-end encryption,服务器无法读取明文。
- 注意:加密字段不支持索引,所以不能出现在
CKQuery的predicate或sortDescriptors里,否则查询会失败。
开发:PIR 让服务器看不到查询内容(14:22)
同态加密(homomorphic encryption)允许在不解密的情况下对密文做加法、乘法运算。Private Information Retrieval(PIR)就是基于它实现的:device 把查询加密后送到 server,server 用同态加密在密文上算出加密的结果送回 device,device 本地解密。整个流程中 server 始终看不到 query 是什么、命中的是哪条数据。在 Apple 自己的产品里 PIR 已经在用,开源代码在 apple/swift-homomorphic-encryption。
开发:减少身份化数据收集(16:05)
- Private Access Tokens 替代 CAPTCHA:设备验证后拿到匿名 token,server 验证 token 不需要身份信息。
- DeviceCheck 给设备绑定最多 2 bit 状态(例如”是否已领取过新人优惠”),状态由 Apple 维护,重装、换机后仍然保留,但 app 拿不到设备标识。
- AdAttributionKit 通过设备签名的 postback 做归因,不需要展示 App Tracking Transparency 弹窗。
测试:把隐私保证写成测试用例(20:50)
- Unit test 验证隐私控制相关的单个函数。
- Integration test 验证子系统之间的数据流(例如 CloudKit 加密字段读写是否正常)。
- UI test 验证用户开关 opt-in 之后,数据流确实跟着改变了。
iOS 15.2 起可以在「设置」里打开 App Privacy Report,自查 app 的数据访问、传感器使用和网络活动,确认与用户预期一致。
部署:Privacy Nutrition Label(22:28)
在 App Store Connect 的 App Privacy 区填写营养标签,描述 app 把哪些数据发送到设备外、用作什么。Xcode 归档时从 archive 上下文菜单选 Generate Privacy Report,会汇总所有 privacy manifest(包括第三方 SDK 的)生成报告,确认营养标签覆盖到位。营养标签必须列出所有”潜在”用途,即便用户可以关掉某个数据收集。营养标签可以在不发版的情况下随时更新。
核心启发
1. 在 kickoff 文档里加一节”隐私保证声明”
为什么值得做:很多团队的 PRD 只写功能不写数据契约,结果开发到一半发现存了不该存的数据。提前写一句”用户位置默认不上传”,后面所有架构决策都有锚点。
怎么开始:用 Apple 的四个 pillar 当 checklist——数据最小化、设备端处理、透明与控制、安全保护,每个 pillar 写一到两条具体的、用户能读懂的承诺。
2. 把广权限 API 换成 system picker
为什么值得做:每个权限弹窗都是一次”用户可能拒绝”的风险点,而 PhotosPicker、ContactPicker、Location Button、UIPasteControl 这类系统组件零弹窗,体验更顺、隐私更好、还能减少 App Privacy Report 里的访问记录。
怎么开始:在代码库里 grep PHPhotoLibrary.requestAuthorization、requestWhenInUseAuthorization、requestRecordPermission 这些调用,看哪些可以改成 picker;新代码里先选 picker,确认无法满足需求再退回到权限 API。
3. 给隐私保证写一条 UI test
为什么值得做:隐私问题在 review 阶段才被发现的成本最高(可能要整改架构);写一条 UI test 让”用户关掉 opt-in 之后,特定接口不再被调用”成为 CI 检查项,回归就在第一时间发现。
怎么开始:挑一条最关键的隐私保证(例如”未 opt-in 不上传照片做模型训练”),用 XCUITest 模拟用户关掉开关,再触发上传流程,断言对应的网络请求或埋点没有发生。
4. 用 CloudKit encryptedValues 替代自建加密
为什么值得做:自己管密钥、轮换、备份是个深坑;CloudKit 加密字段配合 Advanced Data Protection 可以拿到 end-to-end encryption,几乎零接入成本。
怎么开始:把 schema 里敏感字段类型改成 EncryptedString / EncryptedBytes,代码里用 record.encryptedValues["field"] 读写,记得移除这些字段在 CKQuery 中作为 predicate/sort 的用法。
5. 归档前跑 Generate Privacy Report
为什么值得做:第三方 SDK 的 privacy manifest 经常更新,手工对账容易漏;Xcode 的 Generate Privacy Report 会一键汇总所有 manifest,比对到营养标签上。
怎么开始:把”归档后查看 Privacy Report 并核对营养标签”加进发版 checklist;如果某个第三方 SDK 没有 privacy manifest,先去库的 issue 区催,催不到就考虑替换。
关联 Session
- Supercharge device connectivity with Wi-Fi Aware — 用 Wi-Fi Aware 做点对点连接,避免暴露用户网络环境的隐私
- What’s new in UIKit — UIKit 新 API 概览,包括与权限相关的 UI 组件更新
- What’s new in passkeys — passkeys 在 iOS / iPadOS / macOS / visionOS 26 上的增强,无密码登录的隐私基础
评论
GitHub Issues · utterances