Highlight
Apple 将隐私工程归纳为四个支柱:数据最小化(Data Minimization)、端侧处理(On-Device Processing)、透明与控制(Transparency and Control)、安全保护(Security Protections)。iOS 15 围绕这四个支柱推出了 CLLocationButton、CloudKit 加密字段、App Privacy Report 和 iCloud Private Relay 等具体技术,开发者可以直接采用这些能力来提升用户信任。
核心内容
数据最小化:让用户按需分享位置
(03:03)
很多 App 只有一两个功能需要位置信息,但传统做法是在启动时一次性请求「使用期间」位置权限。用户可能因为不信任而拒绝,导致核心功能无法使用。
iOS 15 引入了 CLLocationButton(共享当前位置按钮)。用户点击按钮后,App 获得一次性的位置访问权限,权限持续到用户离开 App。下次再点击同一按钮时,系统不再弹窗,直接返回位置。这个按钮可以自定义背景色、文字颜色和圆角,但必须保持足够的大小和对比度,确保 iOS 能确认是用户主动点击。
这种设计把位置权限从「安装时的一次大决定」变成了「使用时的多次小决定」,降低了用户的心理门槛。
端侧处理:Siri 语音识别完全本地化
(07:01)
语音助手的隐私顾虑主要集中在「我的语音会不会被上传到服务器」。Apple 从 iOS 12 开始在芯片中加入 Neural Engine,逐步将 Siri 的组件搬到设备端。iOS 13 实现了 Siri 语音合成的端侧化,iOS 14 实现了键盘听写的端侧化。
iOS 15 完成了最后一步:所有 Siri 的自动语音识别(ASR)模型都在设备端运行。默认情况下,语音请求的音频不会离开 iPhone 或 iPad。带来的额外好处是响应速度更快,部分请求在离线状态下也能完成。
对于开发者,iOS 15 的 Create ML 框架也支持在 iPhone 和 iPad 上直接训练模型。敏感数据(如照片)可以全程留在设备上,训练出的个性化模型也只服务于当前用户。
透明与控制:App Privacy Report 和 Record App Activity
(14:44)
用户有权知道 App 在什么时候访问了他们的敏感数据。iOS 15 的「记录 App 活动」(Record App Activity)功能会生成一个 JSON 文件,记录 App 访问位置、照片、通讯录等敏感权限的时间点,以及 App 连接的所有域名。
开发者可以在 iOS 15 Beta 中开启这个功能,查看自己的 App 实际行为是否与预期一致。JSON 文件的结构如下:每个访问事件包含时间戳、被访问的数据类型、发起访问的 Bundle ID。域名连接部分会标记是 App 发起的还是网页(WebView)发起的——通过 SafariViewController 和 ASWebAuthenticationSession 的连接会自动标记为网页来源。对于其他第三方内容,开发者可以手动标记:
// 在 NSMutableURLRequest 中标记连接来源
let request = NSMutableURLRequest(url: url)
request.mainDocumentURL = mainDocumentURL
// 在 Network 框架的 NWConnection 中标记
// 或在 URLSession 的 convenience 方法中通过 LinkPresentation 和 AVFoundation 标记
关键点:
mainDocumentURL用于区分主文档请求和第三方资源请求- 未标记的连接默认归类为 App 流量
- WKWebView 新增了传递 NSURLRequest 的方法用于标记
App Privacy Report 将在后续软件更新中向用户开放。开发者现在用 Record App Activity 自查,可以避免上线后被用户发现意外行为。
安全保护:CloudKit 加密字段
(23:03)
CloudKit 之前只对 CKAsset 提供自动加密。iOS 15 扩展了这个能力:字符串、数字、日期、CLLocation 和数组都可以直接加密存储。
// 设备 1:加密后保存到 CloudKit
myRecord.encryptedValues["encryptedStringField"] = "Sensitive value"
// 设备 2:读取后自动解密
let decryptedString = myRecord.encryptedValues["encryptedStringField"] as? String
关键点:
encryptedValues是 CKRecord 的新属性,用法和普通字段一样- 加密在设备端完成,密钥材料存储在 iCloud Keychain 中
- 支持 CloudKit 分享功能,只有 CKShare 的参与者能解密相关字段
- CKAsset 已经默认加密,不能再次设为 encryptedValue
- CKReference 不能被加密,因为服务器需要看到引用关系
iCloud Private Relay:网络层面的隐私保护
(24:29)
Private Relay 是 iCloud+ 的一项新服务,解决的是「网站和网络提供商追踪用户浏览行为」的问题。它的核心设计是:没有任何单一实体(包括 Apple)能同时知道「你是谁」和「你访问了哪个网站」。
实现方式依赖两个随机选择的中继服务器:
- 入口代理(Ingress Proxy):知道用户的真实 IP 地址,但看不到目标网站。它把 IP 地址转换为大致地理区域(如”Bay Area, California”)。
- 出口代理(Egress Proxy):知道目标网站,但看不到用户的真实 IP。它从入口代理提供的地理区域中随机选择一个 IP 地址。
连接使用多层加密,每层由对应的中继服务器剥除。设备是唯一能看到完整信息的节点。访问令牌通过 RSA Blinded Signatures 生成,设备可以兑换网络访问权限而不暴露账户信息。
用户可以在设置中选择两种 IP 地址精度:「保留大致位置」让网站提供本地化内容,或「仅使用国家和时区」获得更强的匿名性。
详细内容
使用 CLLocationButton
(04:49)
import CoreLocationUI
struct ContentView: View {
@StateObject var locationManager = LocationManager()
var body: some View {
LocationButton(.shareCurrentLocation) {
locationManager.requestLocation()
}
.cornerRadius(8)
.labelStyle(.titleAndIcon)
}
}
关键点:
LocationButton是 SwiftUI 专用组件,UIKit 也有对应版本.shareCurrentLocation是系统定义的按钮语义- 按钮外观可自定义,但系统会验证点击的真实性(防止程序化触发)
- 支持 watchOS、iOS、iPadOS 和 macOS Catalyst
验证 App 的网络行为
(16:59)
import Network
// 标记第三方内容连接
let connection = NWConnection(host: "cdn.example.com", port: .https, using: .tls)
// 在 NSMutableURLRequest 中手动标记
let request = NSMutableURLRequest(url: thirdPartyContentURL)
request.mainDocumentURL = mainPageURL
关键点:
mainDocumentURL帮助系统区分核心功能和第三方内容- SafariViewController 和 ASWebAuthenticationSession 的连接自动标记为网页来源
- 定期审查 Record App Activity 的 JSON 输出,确认没有意外连接
CloudKit 加密字段的完整流程
(23:44)
import CloudKit
// 保存加密数据
let record = CKRecord(recordType: "PatientData")
record.encryptedValues["ssn"] = "123-45-6789"
record.encryptedValues["notes"] = "Confidential medical notes"
let container = CKContainer.default()
let database = container.privateCloudDatabase
do {
let savedRecord = try await database.save(record)
print("Encrypted record saved")
} catch {
print("Save failed: \(error)")
}
// 读取加密数据
let query = CKQuery(recordType: "PatientData", predicate: NSPredicate(value: true))
let (matchResults, _) = try await database.records(matching: query)
for (recordID, result) in matchResults {
switch result {
case .success(let record):
let ssn = record.encryptedValues["ssn"] as? String
print("Decrypted SSN: \(ssn ?? "N/A")")
case .failure(let error):
print("Fetch error: \(error)")
}
}
关键点:
encryptedValues的读写语法和普通字段完全一致- 加密/解密由 CloudKit 自动处理,开发者无需管理密钥
- 需要有效的 iCloud 登录状态(
CKAccountStatus.available) - 新状态
temporarilyUnavailable表示账户已登录但未就绪,应引导用户到设置验证
核心启发
1. 用 LocationButton 重构位置权限流程
如果你的 App 只在特定页面需要位置(如地图标注、附近商家),把启动时的位置权限请求替换为页面内的 CLLocationButton。用户点击按钮时才请求位置,授权率通常会显著提升。
开始方式:在需要位置的页面放置 LocationButton,移除 Info.plist 中的 NSLocationWhenInUseUsageDescription 弹窗依赖。测试用户首次点击和后续点击的流程差异。
2. 为 CloudKit 中的敏感数据启用加密字段
任何存储在 CloudKit 中的个人身份信息(PII)、医疗记录、财务数据都应该使用 encryptedValues。成本几乎为零,但隐私承诺提升一个量级。
开始方式:审计现有 CloudKit 数据模型,识别敏感字段。将 record["field"] 替换为 record.encryptedValues["field"]。在 CloudKit Console 中确认字段类型显示为 “Encrypted String” 等前缀。
3. 集成 Record App Activity 到 CI 流程
在每次发版前开启 Record App Activity,运行一遍核心用户流程,检查生成的 JSON 中是否有意外的权限访问或域名连接。这能提前发现 SDK 的隐蔽行为。
开始方式:在测试设备上开启「设置 > 隐私 > 记录 App 活动」,运行 App 的核心场景,导出 JSON 文件。检查 accessedData 和 networkActivity 部分,确认所有访问都在预期范围内。
4. 适配 Mail Privacy Protection 和 Hide My Email
如果你的 App 发送营销邮件,需要意识到 iOS 15 的 Mail Privacy Protection 会预加载远程图片,导致打开率统计失真。IP 地址和设备信息也不再可靠。同时,用户可能用 Hide My Email 提供的随机邮箱地址注册,不要假设邮箱地址可以关联到真实身份。
开始方式:审查邮件营销的数据依赖,减少对打开率像素追踪的依赖。用户注册流程中,如果需要真实姓名或联系方式,明确说明用途并单独请求。
关联 Session
- What’s new in CloudKit — CloudKit 的 async/await API、加密字段和 Zone Sharing 详解
- Get ready for iCloud Private Relay — 如何确保你的 App 和服务器与 Private Relay 兼容
- Meet privacy-preserving ad attribution — 不追踪用户的前提下衡量广告效果
- Why is my app getting killed? — 减少 App 被系统终止的常见原因
评论
GitHub Issues · utterances