Privacy Manifest (PrivacyInfo.xcprivacy) — 自2024年春季起,所有应用程序和第三方 SDK 都必须包含在 iOS 构建中的文件。Apple 要求声明使用受保护 API(Required Reason API)的原因以及收集的数据类型。根据 Apple Developer Documentation,应用程序组成中的每个二进制文件——主目标、框架、Swift Package Manager 依赖项——都必须包含自己的 Privacy Manifest。
要点
Privacy Manifest 是 property list (XML) 格式的 PrivacyInfo.xcprivacy 文件,它声明应用程序收集哪些类型的数据以及为什么使用某些 API。Apple 在提高隐私透明度的倡议框架内引入了清单,类似于 Privacy Nutrition Label,但在代码和二进制文件级别。
清单的主要目的是记录所谓的 Required Reason API 的使用情况。这些是可以用于获取设备数字指纹(fingerprinting)的 API 类别,因此每次使用都必须附带来自 Apple 批准列表的具体原因声明。
自2024年5月1日起,Apple 拒绝不包含 Privacy Manifest 的构建。这既适用于应用程序的主目标,也适用于所有嵌入的 SDK 和依赖项。根据 WWDC 2023,清单对于通过 App Store 分发的所有应用程序都是强制性的。
清单必须存在于每个二进制文件中——不仅在主应用程序中,而且在每个框架和静态库中。如果第三方 SDK 未提供清单,开发人员必须手动添加或联系 SDK 提供者进行更新。
在清单出现之前,开发人员可以使用广泛的系统 API(访问文件系统、日期/时间、系统日志)而无需透明解释使用目的。这通过第三方 SDK 造成了数据泄露的风险,这些 SDK 可以在开发人员不知情的情况下收集信息。
根据 Mysk Inc. (2023) 的数据,许多流行的 SDK 在没有明确需要的情况下使用 API 获取设备指纹——例如,读取 uptime 时间以确定时区或扫描目录以收集元数据。Privacy Manifest 强制声明每个此类操作。
除了 Required Reason API 之外,清单还包括 NSPrivacyTracking 部分——指示应用程序是否使用跟踪的标志(需要 ATT)和 NSPrivacyCollectedDataTypes——以 Privacy Nutrition Label 格式收集的数据类型列表。
Privacy Nutrition Label 是 App Store 中应用程序页面上的用户友好显示,显示应用程序收集哪些数据。Privacy Manifest 是二进制文件内的一个技术文件,由 Apple 自动检查。如果清单中的数据与 App Store Connect 中的标签不匹配,Apple 将拒绝构建。
因此,清单是 Apple 检查系统的真相来源。隐私标签基于它自动生成,但开发人员必须保持两种表示都是最新的。发布后更改清单需要提交新的构建进行审核。
PrivacyInfo.xcprivacy 文件使用 property list 格式,根类型为 Dictionary。顶级的主要键:NSPrivacyTracking(布尔值)、NSPrivacyTrackingDomains(数组)、NSPrivacyCollectedDataTypes(数组)、NSPrivacyAccessedAPITypes(数组)。
NSPrivacyTracking 键——指示应用程序是否使用跟踪的布尔值(需要 ATT)。如果为 true,还必须在 Info.plist 中指定 NSUserTrackingUsageDescription。NSPrivacyTrackingDomains 键——应用跟踪的域数组。
NSPrivacyCollectedDataTypes 键——字典数组,每个字典描述收集的数据类型:类别(NSPrivacyCollectedDataType)、强制性(NSPrivacyCollectedDataTypeLinked)、目的(NSPrivacyCollectedDataTypePurposes)。目的包括:第三方标记、分析、产品开发和内容个性化。
NSPrivacyAccessedAPITypes 键——每个 Required Reason API 类别的字典数组:类别(NSPrivacyAccessedAPITypeReasons)——来自 Apple 批准列表的具体原因和 NSPrivacyAccessedAPIType——API 类别的标识符。
<!-- PrivacyInfo.xcprivacy -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
</array>
</dict>
</plist>
数据类型分为几个类别:联系信息(姓名、电子邮件、电话)、财务(支付数据)、标识符(IDFA、用户 ID)、使用数据(日志、点击)、诊断(crash logs)和用户内容(照片、视频、文件)。每个类别额外标记为「与用户关联」(linked)或「不关联」(not linked)。
Linked 意味着数据与用户帐户关联;not linked——数据已聚合或匿名化。Apple 显示隐私标签的方式取决于此标记:linked 数据标记为黄色,not linked——绿色。
Apple 已经指定了几个 API 类别,每次使用都必须附带来自批准列表的具体原因。原因是一个唯一代码,例如 C617.1 或 35F9.1,对应于 Apple 允许的特定目的。
NSPrivacyAccessedAPICategoryFileTimestamp 类别包括用于访问文件时间戳(creationDate、modificationDate)的 API。原因:C617.1(文件同步)、3B52.1(备份)、0A2A.1(反欺诈)。
NSPrivacyAccessedAPICategoryDiskSpace 类别包括用于检查磁盘可用空间的 API(NSFileManager。检查(NSFileManager)。原因:E174.1(缓存管理)、85F4.1(内容下载)、B728.1(诊断)。
NSPrivacyAccessedAPICategorySystemBootTime 类别包括访问系统启动时间(uptime)的 API。只有一个原因:35F9.1(测量会话时间用于分析)。任何其他用途都被视为 fingerprinting。
每个类别有 1 到 5 个批准的原因。开发人员有义务选择与 API 实际使用最匹配的原因。提供不实原因可能导致构建被拒绝或帐户被封锁。
自2024年5月1日起,所有第三方 SDK——无论是二进制还是开源——都必须在其包中包含 Privacy Manifest。Apple 检查所有依赖项的清单,如果至少有一个 SDK 没有清单,构建将被拒绝。
大型 SDK(Firebase、Adjust、AppsFlyer、Facebook SDK)已经更新了它们的包。如果您的项目使用不太知名的 SDK,请检查其版本,并在必要时更新它或联系开发人员。临时解决方案是手动将 SDK 的清单添加到项目中,但由于更新时的不一致性,不建议这样做。
Privacy Info.xcprivacy 可以通过 Xcode 15+ 的内置模板或手动创建。我们将考虑这两种选项。
Xcode 15 中添加了 Privacy Manifest 模板:File → New → File → Resource → Privacy Manifest。Xcode 创建一个带有基本结构的文件,并填充 NSPrivacyTracking 以及 API 和数据的空数组。创建后,需要手动为每个使用的 Required Reason API 指定原因。
模板自动将文件添加到主目标。如果项目有多个目标(Extensions、Watch app),则需要将清单添加到每个目标。Xcode 在编译阶段不检查依赖项中是否存在清单——仅在归档阶段检查。
// AppDelegate:在开发阶段检查清单
import Foundation
func validatePrivacyManifest() {
guard let path = Bundle.main.path(
forResource: "PrivacyInfo", ofType: "xcprivacy"
) else {
print("PrivacyInfo.xcprivacy not found")
return
}
guard let dict = NSDictionary(contentsOfFile: path)
else { return }
print("Privacy manifest loaded: \(dict.count) keys")
}
您可以手动创建 PrivacyInfo.xcprivacy 作为普通的 XML property list 文件。为此,创建一个名为 PrivacyInfo.xcprivacy 的文件,编写标准的 plist 头部,并添加带有必要键的根字典。确保文件已添加到目标(Target Membership)。
手动方法在需要将清单添加到静态库或 Package Manager 依赖项而不使用 Xcode 界面时很有用。编译后,通过 nm 命令或归档检查文件是否已进入二进制文件。
在将构建发送到 App Store 之前,需要检查 Privacy Manifest 是否正确和完整。Apple 提供了几个检查工具。
在归档项目时(Product → Archive),Xcode 执行清单检查。如果检测到错误——缺少清单、不正确的原因、空类别——归档以错误结束。错误日志显示在 Issue navigator 中,并指出有问题的 SDK 和 API 类别。
额外的检查由 App Store Connect 在上传构建时执行。如果验证失败,构建将被拒绝,并发送一封自动电子邮件,其中包含有问题的 API 标识符和推荐的修复方法。
为了自动化检查,使用分析二进制文件中 Required Reason API 使用情况的脚本。Apple 提供了 libtool 工具和符号检查脚本,但社区开发了更方便的工具。
# 在二进制文件中搜索 FileTimestamp API 的使用
nm MyApp.app/MyApp | grep "NSFileCreationDate"
# 检查应用程序中是否存在 PrivacyInfo.xcprivacy
find MyApp.app -name "*.xcprivacy"
最常见的错误——其中一个 SDK 中缺少清单。即使主目标包含清单,Apple 也会单独检查所有二进制文件。第二常见的错误——错误的原因代码:将一个 API 类别的原因与另一个类别的 API 一起使用。
第三个错误——过度声明实际上未使用的 API。开发人员添加所有可能的原因「以防万一」,这会让 Apple 审核人员感到担忧。只添加实际使用的类别。使用 Xcode 的静态分析器进行检查。
常见问题
自2024年5月1日起,Apple 拒绝任何没有清单的构建。错误出现在上传到 App Store Connect 的阶段。旧应用程序不会被阻止,但更新需要强制包含清单。
是的,清单对所有 Apple 平台都是强制性的:iOS、iPadOS、macOS、tvOS、watchOS 和 visionOS。Required Reason API 的要求对所有平台都是统一的,尽管 API 集可能略有不同。
如果 SDK 未提供清单,请创建一个单独的 PrivacyInfo.xcprivacy 文件,并将其添加到项目中的 SDK 组。根据 SDK 的文档为该 SDK 使用的 API 指定原因。
是的。在自动检查中,Apple 比较声明的原因与实际 API 调用。不匹配会导致拒绝。在手动检查中,审核人员可能会要求解释。
是的,Apple 检查所有依赖项的清单,包括 SPM。包提供者有义务在其仓库中包含 PrivacyInfo.xcprivacy。Xcode 在归档时会警告缺少的清单。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。