跳到主要内容

AI 生成的 iOS 代码能上架吗?上架前检查清单:隐私、权限与审核

AI 生成的 iOS 代码能编译不等于能上架,常出问题的是权限用途说明、隐私清单、高于部署目标的 API、编出来的非公开方法和日志里的用户数据。本文给出按类别整理的上架前检查清单和让 AI 自查的提示词。

AI 生成的 iOS 代码能编译、能跑,不代表能上架。最容易出问题的是 AI 不会主动处理的部分:权限用途说明、隐私清单、高于部署目标的 API、编出来的非公开方法、日志里的用户数据,以及内购、登录、账号删除三条审核规则。做法是用 Release 包按下面的上架前检查清单逐项核对,每项都写了怎么查,文末附一段让 AI 先自查的提示词。规则以 Apple 官方文档和《App Store 审核指南》为准,截至 2026 年 10 月。

这份上架前检查清单怎么用?

  1. 对 Release 包做检查。Debug 包里有调试开关、测试地址和预览代码,检查结果不算数。
  2. 按顺序过一遍总表,每项打勾。打不了勾的,跳到下面对应的小节看具体做法。
  3. 每次让 AI 改完大块功能就复查一次,不要攒到提交前。新加的权限、SDK、网络请求最容易漏。

审核结果由 Apple 决定,这份清单只能帮你排除常见的、可以提前发现的问题。

总表(可打印)

类别 检查项 怎么检查 详见
隐私与权限 每个用到的权限都有用途说明,写清具体场景 搜 AVCaptureDevice、PHPhotoLibrary、CLLocationManager 等调用,逐个对照 Info.plist 的 NS…UsageDescription 178
隐私与权限 拒绝授权后 App 仍能用其他功能 真机上对每个授权弹窗点「不允许」,把主流程走一遍 176
隐私与权限 必需原因 API 都在隐私清单里声明 搜 UserDefaults、creationDate、systemUptime 等关键字,对照 PrivacyInfo.xcprivacy 182
隐私与权限 跟踪用户前弹 ATT,有 NSUserTrackingUsageDescription 搜 advertisingIdentifier、requestTrackingAuthorization,查广告和归因 SDK 176
隐私与权限 用户数据发给第三方 AI 服务前告知并取得同意 搜请求 AI 接口的代码,看发送前有没有说明和同意步骤 176
第三方 SDK Apple 名单上的 SDK 都带隐私清单,二进制形式的带签名 解压 ipa 列出 PrivacyInfo.xcprivacy 和 .framework,对照官方名单 182
第三方 SDK AI 推荐的库真实存在、仍在维护 去库的官方仓库看最近的发布和 Issues 下文
API 没有高于部署目标的 API 用最低部署目标编译,看 is only available in iOS xx or newer 190
API 已弃用 API 的警告都处理了 编译日志搜 deprecated 下文
API 没有编出来的方法、没有非公开 API 编译报 has no member;搜 NSClassFromString、performSelector、valueForKey:@"_ 下文
数据 新版本能读旧版本留下的数据 先装旧版本产生数据,再覆盖安装新版本 202
数据 令牌、密码存在 Keychain,不在 UserDefaults 或文件里 搜 token、password 附近的 UserDefaults、write(to: 下文
数据 日志不打印手机号、令牌等隐私 搜 print(、NSLog(、privacy: .public 194
网络 没有整体关闭 ATS,例外只给必要的域名 Info.plist 搜 NSAllowsArbitraryLoads 213
网络 源码里没有硬编码的密钥、测试地址 搜 sk_、secret、apiKey、http://、192.168.、localhost 下文
审核 数字内容、会员用 App 内购买(3.1.1) 列出所有付费入口,逐个判断属于哪一类 176、191
审核 有第三方登录时提供等效登录选项(4.8) 看登录页有几种登录方式 201
审核 能注册就能在 App 内删除账号(5.1.1(v)) 在 App 里找删除入口,走一遍删除流程 176
审核 没有占位文字、空页面、指向测试环境的接口(2.1) 全局搜 TODO、Lorem、test、staging 176
构建 用 Release 配置、发布证书构建 看构建命令和签名信息 130
构建 版本号、构建号比上一次大 解压 ipa 打开 Info.plist 看两个号 185
构建 预览代码、调试开关不在 Release 包里 搜 #Preview、#if DEBUG 的范围 190
构建 加密出口合规已声明 Info.plist 里有 ITSAppUsesNonExemptEncryption 下文
构建 推送等能力在 entitlements 和描述文件里一致 打开包里的 embedded.mobileprovision 看权限 195、186

检查前要准备什么?

下面每一节都会用到这两样。

Release 包。在 VS Code 运行「KXBuild: 生成 App Store ipa」,或者在命令行构建,得到 build\MyApp.ipa。

1kxbuild build .\MyApp.xcodeproj -c Release --export app-store

ipa 是 zip 格式,用系统自带的 tar 解压到一个临时目录。解压后的 Payload\MyApp.app\Info.plist、PrivacyInfo.xcprivacy、embedded.mobileprovision 都能在 VS Code 里双击打开,用 属性列表编辑器查看,二进制格式也能读。

1mkdir .\ipa-check
2tar -xf .\build\MyApp.ipa -C .\ipa-check

源码搜索。搜索范围只放自己的源码目录,跳过 Pods、.build、node_modules。PowerShell 的写法如下,后文只给关键字。

1Get-ChildItem .\MyApp -Recurse -Include *.swift,*.m,*.mm |
2  Select-String -Pattern '关键字1|关键字2' |
3  Select-Object Path, LineNumber, Line

Linux 或 WSL 上用 grep。

1grep -rnE --include='*.swift' --include='*.m' --include='*.mm' '关键字1|关键字2' MyApp

也可以直接用 VS Code 的全局搜索,打开正则开关,在「要排除的文件」里填 Pods,.build,node_modules。

AI 生成的代码在隐私与权限上常漏什么?

用途说明。AI 加了访问相机、相册、定位、麦克风、通讯录的代码,常常不会同时改 Info.plist。缺少用途说明,App 访问时会被系统终止,上传时会收到 ITMS-90683。

搜下面的关键字,每命中一类,就到 Info.plist 里找对应的 NS…UsageDescription 键,并确认文字写的是具体场景,例如「用于扫描快递单号」,而不是「需要使用相机」。键名对照见 Info.plist 权限用途说明。

1AVCaptureDevice|PHPhotoLibrary|PHPickerViewController|CLLocationManager|AVAudioSession|CNContactStore|EKEventStore|CBCentralManager|HKHealthStore

PHPickerViewController 不需要相册权限,命中它说明可以不申请完整相册访问。审核指南 5.1.1(iii) 要求只申请核心功能需要的数据,能用系统选择器就别申请完整权限。

必需原因 API。几乎所有 App 都用 UserDefaults,所以几乎都要有 PrivacyInfo.xcprivacy。关键字和原因码的对照见 隐私清单怎么写。按 Apple 官方说明,自 2024 年 5 月 1 日起,用了这类 API 却没有声明原因的 App 不被 App Store Connect 接受。

1UserDefaults|@AppStorage|creationDate|modificationDate|systemUptime|mach_absolute_time|volumeAvailableCapacity|activeInputModes

ATT。搜 advertisingIdentifier 和 requestTrackingAuthorization。代码或广告、归因 SDK 要跟踪用户,就要先调用 requestTrackingAuthorization,并在 Info.plist 里写 NSUserTrackingUsageDescription。App Store Connect 的「App 隐私」问卷要和实际行为一致。

发给 AI 服务的数据。审核指南 5.1.2(i) 要求,把个人数据分享给第三方(包括第三方 AI)前要明确告知并取得同意。App 里有「把聊天内容、照片发给大模型」这类功能的,确认发送前有说明和同意步骤,隐私政策里也写上。

AI 推荐的第三方 SDK 要查什么?

Apple 维护一份常用第三方 SDK 名单。新 App 包含名单上的 SDK,或者更新版本新加入名单上的 SDK,要带上它的隐私清单;以二进制形式引入的,还要带签名。名单里有 Alamofire、Firebase 系列、Kingfisher、SDWebImage、Lottie、hermes、Flutter 等常见库。

在解压目录里列出所有框架和隐私清单,对照名单看缺哪个。

1Get-ChildItem .\ipa-check -Recurse -Filter PrivacyInfo.xcprivacy | Select-Object FullName
2Get-ChildItem .\ipa-check\Payload\MyApp.app\Frameworks -Directory | Select-Object Name

AI 推荐库时有两个常见问题:推荐一个已经多年不更新、没有隐私清单的旧版本,或者给出一个根本不存在的包名。加依赖前去库的官方仓库确认三件事:包名和仓库地址对得上,最近一年有发布,最新版本带 PrivacyInfo.xcprivacy。

怎么查高于部署目标的 API 和非公开 API?

部署目标与已弃用 API

AI 默认按最新的 iOS 写代码。工程的最低部署目标低于某个 API 的要求时,编译直接报错。下面是用 iOS 16 作部署目标编译一段 AI 生成代码的实际输出。

1error: 'ContentUnavailableView' is only available in iOS 17.0 or newer
2warning: 'setStatusBarHidden(_:with:)' was deprecated in iOS 9.0: Use -[UIViewController prefersStatusBarHidden]
3error: value of type 'UIView' has no member 'applyBlurEffectAutomatically'

三行分别对应三类问题。第一类用 if #available(iOS 17, *) 包起来,并给低版本写替代界面,写法见 用 AI 写 SwiftUI 的常见坑。第二类按警告里的提示换成新 API,弃用警告现在能编过,但以后的 SDK 可能直接删掉。第三类是 AI 编出来的方法,下一节说怎么处理。

先确认工程的部署目标是多少,再看编译日志。命令行可以导出构建设置查看。

1kxbuild project settings dump .\MyApp.xcodeproj | findstr IPHONEOS_DEPLOYMENT_TARGET

截至 2026 年 10 月,Apple 的上传要求是:自 2026 年 4 月 28 日起,上传的 App 要用 iOS 26 SDK 构建;自 2026 年 9 月 9 日起,iOS App 的部署目标要是 iOS 13 或更高。部署目标具体定多少,见 最低支持 iOS 版本怎么定。

编出来的方法与非公开 API

AI 有时会写出看起来很合理、但 SDK 里根本没有的方法。Swift 代码里这类调用编译不过,报 has no member,这是最可靠的核验方式。遇到这类报错,不要让 AI 「换个写法让它编过」,先去 Apple 开发者文档按类名搜一下,确认有没有实现这个功能的公开 API。

更危险的是能编过的写法。用字符串动态调用的方法,编译器不检查它是否存在、是否公开,审核指南 2.5.1 要求只用公开 API。搜下面这些关键字,逐个确认调用的名字都能在官方文档里查到。

1NSClassFromString|NSSelectorFromString|performSelector|value\(forKey: *"_|valueForKey:@"_|setValue\(.*forKey: *"_|dlopen|dlsym

以下划线开头的键名和类名基本都是非公开的。审核被拒信里通常会列出具体的符号名,按符号名搜自己的代码和依赖,见 审核被拒的 12 个原因。

数据存储和日志要注意什么?

旧数据兼容。AI 改数据模型时只看当前代码,不知道用户手机上还存着旧版本写的数据。给 Codable 结构体加了一个非可选字段,旧的 JSON 里没有这个键,解码就会失败,用户的设置或缓存全部丢失。新字段用 decodeIfPresent 给默认值。

 1struct UserSettings: Codable {
 2    var fontSize: Int
 3    var darkMode: Bool
 4    var language: String
 5
 6    enum CodingKeys: String, CodingKey { case fontSize, darkMode, language }
 7
 8    init(from decoder: Decoder) throws {
 9        let c = try decoder.container(keyedBy: CodingKeys.self)
10        fontSize = try c.decode(Int.self, forKey: .fontSize)
11        darkMode = try c.decode(Bool.self, forKey: .darkMode)
12        language = try c.decodeIfPresent(String.self, forKey: .language) ?? "zh-Hans"
13    }
14}

检查方法是在真机上先装上一个正式版本,用一阵产生数据,再直接覆盖安装新版本,不要先删 App。还要留意 UserDefaults 的键名有没有被 AI 改掉。用 SwiftData 的,模型变化要写迁移,见 SwiftData 本地数据持久化教程。

敏感信息放 Keychain。AI 很喜欢把登录令牌写进 UserDefaults,因为写起来最短。UserDefaults 是明文的 plist 文件,令牌、密码、密钥要放 Keychain。

 1import Foundation
 2import Security
 3
 4enum TokenStore {
 5    private static let service = "com.example.myapp.auth"
 6    private static let account = "accessToken"
 7
 8    static func save(_ token: String) -> Bool {
 9        let base: [String: Any] = [
10            kSecClass as String: kSecClassGenericPassword,
11            kSecAttrService as String: service,
12            kSecAttrAccount as String: account,
13        ]
14        SecItemDelete(base as CFDictionary)
15        var item = base
16        item[kSecValueData as String] = Data(token.utf8)
17        item[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
18        return SecItemAdd(item as CFDictionary, nil) == errSecSuccess
19    }
20
21    static func load() -> String? {
22        let query: [String: Any] = [
23            kSecClass as String: kSecClassGenericPassword,
24            kSecAttrService as String: service,
25            kSecAttrAccount as String: account,
26            kSecReturnData as String: true,
27            kSecMatchLimit as String: kSecMatchLimitOne,
28        ]
29        var result: AnyObject?
30        guard SecItemCopyMatching(query as CFDictionary, &result) == errSecSuccess,
31              let data = result as? Data else { return nil }
32        return String(decoding: data, as: UTF8.self)
33    }
34}

kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly 表示开机首次解锁后可读,且不会迁移到其他设备。搜 token|password|secret 附近有没有 UserDefaults、@AppStorage、write(to:,有就改成上面的写法。

日志不打印隐私。AI 调试时会到处加 print,把请求参数、手机号、令牌原样打出来。上架前搜 print(、NSLog(,删掉调试输出,要保留的日志改用 Logger。Logger 插值里的字符串默认隐去,只有你确认不敏感的内容才加 privacy: .public。

1import os
2
3private let authLog = Logger(subsystem: "com.example.myapp", category: "auth")
4
5func logLogin(userID: String, phone: String, success: Bool) {
6    authLog.notice("登录结果 \(success) 用户 \(userID, privacy: .private(mask: .hash))")
7    authLog.debug("手机号 \(phone, privacy: .private)")
8}

再搜一遍 privacy: .public,看后面跟的是不是账号、手机号、令牌这类值。Logger 的级别和隐私选项见 iOS 真机日志怎么看。

网络配置和硬编码密钥怎么查?

ATS。AI 遇到 http 请求失败,最常给的办法是在 Info.plist 里加 NSAllowsArbitraryLoads = YES,整个 App 都不再受 ATS 保护。按 Apple 文档,这类例外提交审核时要说明理由,可能触发额外审核。

打开解压出来的 Info.plist,看 NSAppTransportSecurity 下有什么:能把服务器改成 https 就删掉例外;只有个别域名改不了,就只给它们开 NSExceptionDomains。写法见 iOS 网络请求调试。

硬编码的密钥和地址。AI 生成的示例常常直接把 API Key 写在代码里。打进 App 的字符串谁都能从包里读出来,第三方服务的私钥要放在你的服务端,App 只调用你自己的接口。搜下面的关键字,源码和解压后的 App 都搜一遍。

1sk_|secret|apiKey|api_key|Bearer |PRIVATE KEY|http://|192\.168\.|localhost|staging|test\.

在解压目录里也可以直接搜可执行文件,确认字符串没有残留。

1grep -a -o -E 'sk_[A-Za-z0-9]{8,}|192\.168\.[0-9.]+|http://[A-Za-z0-9.-]+' ipa-check/Payload/MyApp.app/MyApp | sort -u

哪几条审核规则 AI 不会主动考虑?

这三条规则 AI 基本不会主动考虑,要你自己对照。条款原文见 App Store 审核指南,被拒信的读法和回复要点见 审核被拒的 12 个原因。

条款 要求 怎么检查
3.1.1 内购 解锁 App 内的功能、会员、虚拟币、付费内容要用 App 内购买,不能用授权码等自有方式解锁 列出所有付费入口。数字内容走 StoreKit;实物商品和 App 外使用的服务按 3.1.3(e) 用其他支付方式
4.8 登录服务 用第三方或社交登录作主账号的 App,还要提供一种只收集姓名和邮箱、允许隐藏邮箱、不为广告收集交互数据的登录方式 登录页只有微信、Google 这类登录时,加上通过 Apple 登录
5.1.1(v) 账号删除 支持创建账号的 App 必须在 App 内提供删除账号;和账号无关的功能要允许不登录使用 在设置里找到删除入口,确认删除的是账号和关联数据,而不只是停用

AI 写支付功能时,常会直接接入网页支付或第三方支付 SDK。这在售卖数字内容时会触发 3.1.1。接 App 内购买的完整步骤见 StoreKit 2 接入应用内购买,通过 Apple 登录见 接入「通过 Apple 登录」,它还讲了删除账号时怎么撤销令牌。

AI 生成的界面常带占位内容。全局搜 TODO|FIXME|Lorem|示例|测试数据,审核指南 2.1 要求提交的是最终版本。

上架构建前最后确认哪些项?

  • Release 与发布证书。上架包用 Release 配置和 Apple Distribution 证书、App Store 类型的描述文件构建,流程见 在 Windows 上上架 App Store。
  • 版本号与构建号。打开解压出来的 Info.plist,看 CFBundleShortVersionString 和 CFBundleVersion,构建号要比上一次上传的大,规则见 版本号与构建号。
  • 调试代码。搜 #if DEBUG,确认测试地址、调试菜单、跳过登录的开关都在里面;#Preview 里的示例数据不要被正式代码引用,见 用 AI 写 SwiftUI 的常见坑。
  • 加密出口合规。只用 HTTPS 和系统加密的 App,通常在 Info.plist 里把 ITSAppUsesNonExemptEncryption 设为 NO。按 Apple 文档,缺这个键,每次上传新版本 App Store Connect 都会让你回答一遍出口合规问卷。是否属于豁免范围按你实际用的加密判断。
  • 能力和描述文件。双击打开解压出来的 embedded.mobileprovision,看权限里有没有你用到的能力,例如推送的 aps-environment。AI 加了注册推送的代码却没开能力,上传时会收到 ITMS-90078,推送凭据见 推送 p8 与 p12。上传阶段的报错码见 ITMS 错误码速查表。

怎么让 AI 先自查一遍?

把下面这段连同相关代码、Info.plist、PrivacyInfo.xcprivacy 一起交给 AI,让它先按清单列出问题,你再逐条核实。AI 的回答只是线索,每一条都要用上面的编译和搜索方法确认。

 1你是 iOS 上架审核检查员。请检查我提供的代码和配置文件,只列问题,不要直接改代码。
 2
 3工程信息:
 4- 最低部署目标:iOS 16.0
 5- 语言与界面:Swift 6,SwiftUI
 6- 有账号注册:是;登录方式:手机号、微信
 7- 付费内容:会员订阅
 8
 9逐项检查并按「文件:行号 | 问题 | 依据 | 建议」输出:
101. 每个访问相机、相册、定位、麦克风、通讯录的调用,Info.plist 里是否有对应的 NS…UsageDescription,文字是否写了具体场景。
112. 用到的 UserDefaults、文件时间戳、系统启动时间、磁盘空间、活动键盘 API,PrivacyInfo.xcprivacy 是否声明了对应类别。
123. 是否使用高于 iOS 16.0 的 API 且没有 #available 判断。
134. 是否有你不确定在 Apple SDK 中存在的方法,或用字符串动态调用的方法(NSClassFromString、performSelector、以下划线开头的键)。列出来,标明「需要人工核实」,不要假设它存在。
145. 令牌、密码是否存在 UserDefaults 或文件里;print、NSLog、Logger 是否输出了手机号、令牌等个人信息。
156. Info.plist 是否有 NSAllowsArbitraryLoads;代码里是否有硬编码的密钥、http 地址、内网 IP。
167. 对照 App Store 审核指南 3.1.1、4.8、5.1.1(v):付费方式、登录方式、App 内删除账号是否合规。
178. 是否有 TODO、占位文字、测试环境地址。
18
19没有发现问题的项写「未发现」。不确定的写「不确定」,不要编造 API 名称或审核条款。

工程信息那几行换成你自己的。让 AI 改代码之前,先把它列出的「需要人工核实」项查清楚,再用上面的上架前检查清单把 AI 生成的 iOS 代码完整过一遍。

常见问题

AI 写的 iOS 代码上架前最需要检查什么?

先查四件事:每个权限都有具体的用途说明,PrivacyInfo.xcprivacy 声明了用到的必需原因 API,用最低部署目标编译没有可用性报错,付费、登录、账号删除符合审核指南 3.1.1、4.8 和 5.1.1(v)。这几类问题 AI 最少主动处理。

怎么判断 AI 给的方法是不是真的存在?

先编译,Swift 代码里不存在的方法会直接报 has no member。能编过但用字符串调用的写法(NSClassFromString、performSelector、以下划线开头的键),要去 Apple 开发者文档逐个搜名字,查不到的就当作非公开 API 删掉。

AI 把 NSAllowsArbitraryLoads 设成 YES 能上架吗?

按 Apple 文档,这个例外提交审核时要说明理由,可能触发额外审核。更稳妥的做法是把服务器改成 https 并删掉它;只有个别域名改不了时,只给这些域名配置 NSExceptionDomains。

登录令牌可以存在 UserDefaults 里吗?

不建议。UserDefaults 是明文的 plist 文件,令牌、密码这类敏感信息应该存进 Keychain,并按需要选择 kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly 这类可访问性。

让 AI 自查之后还需要人工检查吗?

需要。AI 的自查结果只能当线索,它可能漏报,也可能编出不存在的规则。每一条都用编译、源码搜索、解压 ipa 和真机测试核实,审核规则以 Apple 官方原文为准。