处理 iOS 应用提审,很多团队把它当成“最后一步”的打包上传操作。实际上,真正影响提审结果的工作分布在开发者账号、证书、构建打包、App Store Connect 元数据、审核回复这条完整链条上。任何一环出错,都会出现“上传成功但审核被拒”这类结果。这篇文章围绕 iOS 应用提审的完整操作链路来写:从证书和描述文件准备开始,到 Xcode 构建、命令行上传、提审材料填写,再到审核被拒后的排查与处理。内容适合正在准备上架 iOS 应用、经常被提审问题卡住的开发者和移动端负责人。
1. iOS 提审不是打包上传,而是整条发布链路
1.1 一次常规提审会经过哪些环节
常见的 iOS 提审流程可以拆成 8 个环节:
- 确认开发者账号有效且类型正确。
- 在 Apple Developer 后台创建 App ID,开启所需能力。
- 生成或更新证书与描述文件。
- 用 Xcode 打包并上传构建版本。
- 在 App Store Connect 填写应用信息和审核材料。
- 选择构建版本并提交审核。
- 审核期间处理和回复审核团队消息。
- 审核通过后管理发布方式与版本更新。
每个环节都有独立的失败点。上传构建失败通常只报 Xcode 错误或 ITMS 错误,而审核被拒则会在邮件里给出 Guideline 编号。后续排查要先判断问题出在哪个环节,不能只在 App Store Connect 里反复点提交。
1.2 提审前你必须想清楚的几个决策
几个常见决策:使用 TestFlight 内测还是直接提审;使用 Xcode 自动管理签名还是手动管理;审核期是否需要申请加急审核;提审使用的构建版本是否与线上版本兼容;发布节奏是采用“手动发布”还是“自动发布”。
| 决策项 | 推荐做法 | 原因 |
|---|---|---|
| 内测与正式提审 | 先走 TestFlight 邀请内部和外部测试 | 提前发现安装、崩溃、权限问题 |
| 签名方式 | 团队小优先 Xcode 自动签名 | 减少证书过期和描述文件不匹配 |
| 构建发布方式 | 常用“手动发布” | 审核通过后可选择合适时间上架 |
| 加急审核 | 仅当线上事故或固定活动窗口时申请 | 滥用可能影响后续审核优先级 |
1.3 常见误解:提审通过不等于发布完成
提审通过后,如果配置的是“手动发布”,还需要在 App Store Connect 点击“发布此版本”才会正式上线。自动发布的版本,则会在审核状态变为“准备销售”后自动上架。这是很多团队第一次上架时容易误解的地方,也是发布计划被打乱的高频原因。
2. 提审前置工作:账号、证书与描述文件
2.1 开发者账号类型决定了提审入口
个人开发者账号可以提交审核,但 App 名称、隐私信息等由个人管理;公司开发者账号需要填写 D-U-N-S 编码,审核时可能要求提供更多组织信息。企业开发者账号不能上架 App Store,只能用于内部发布。提审前先确认账号类型是不是正确的 App Store 发布账号。这看起来基础,却是一些团队在提交时看不到“提交审核”按钮的原因之一。
2.2 证书和描述文件的作用与检查方式
证书用于证明 App 由有效开发者签名,描述文件把 App ID、设备和证书绑定在一起。提审阶段主要关心:
- Distribution 证书,即 App Store 和 Ad Hoc 证书。
- App Store 描述文件。
- Development 证书和开发描述文件用于本地调试。
检查方式:在 Developer 后台 Certificates, Identifiers & Profiles 页面,确认证书状态为 Active,描述文件状态同样为 Active。如果过期,Xcode 会自动报 “No profiles for ...” 错误。
命令行检查 App 签名:
codesign -dv --verbose=4 /path/to/YourApp.app如果签名无效,会看到 “code object is not signed at all” 或 “invalid signature” 等提示。
2.3 App ID 与能力配置
创建 App ID 时需要开启推送通知、App Groups、Sign in with Apple、相关能力。提审前要对照 App 实际功能检查,因为审核期间如果 App 调用了未声明能力或 API,可能触发 Guideline 2.5.1 等审核问题。
示例:开启了 Push Notifications,但 App 里没有申请通知权限,审核问题不一定出现;反过来,App 里申请了通知权限,但后台没有配置对应证书和模式,审核时可能因无法收到推送被判定功能不完整。
2.4 常见坑:证书过期、描述文件不匹配、Bundle ID 不一致
| 问题现象 | 可能原因 | 检查与处理 |
|---|---|---|
| Xcode 报 “No profiles for ... were found” | 描述文件未配置或过期 | Developer 后台重新生成描述文件 |
| 上传时提示 Bundle ID 错误 | Xcode 工程 Bundle ID 与后台 App ID 不一致 | 统一 Bundle ID,并重新生成描述文件 |
| App 安装在真机后闪退 | 证书签名过期或能力配置缺失 | 检查签名,核对 entitlements |
| 提审后审核员反馈“无法验证 App” | 使用了不稳定的内部环境 | 保留可访问的演示环境 |
证书过期不是“重新生成一份”就结束,更新证书后要同步修改本地钥匙串中的私钥和 Xcode 的签名配置。很多团队在证书页面重新生成后,发现上传依旧失败,就是因为本机钥匙串里没有对应的私钥。
3. 用 Xcode 和命令行做好提审前的构建
3.1 版本号与构建号:提审最容易的失败点
App Store Connect 要求版本号不能重复,构建号也要唯一。比如:
- CFBundleShortVersionString = 1.2.0,这是展示给用户的版本号。
- CFBundleVersion = 100,这是内部构建号,每次上传必须比上一次大。
如果同一个构建号已经存在于某个版本,App Store Connect 会拒绝导入。解决办法是修改构建号并重新归档。
查看当前工程配置:
/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" YourApp/Info.plist /usr/libexec/PlistBuddy -c "Print CFBundleVersion" YourApp/Info.plist3.2 Info.plist 权限描述与隐私清单检查
凡是涉及相机、相册、定位、麦克风、日历、通讯录、蓝牙等隐私权限,必须在 Info.plist 中声明目的字符串,否则 App 在系统弹窗时直接崩溃。审核阶段也容易因为权限弹窗没有说明用途被拒。
| 功能 | Info.plist 键 | 示例描述 |
|---|---|---|
| 相机 | NSCameraUsageDescription | 说明用于拍摄照片或扫描二维码 |
| 相册读取 | NSPhotoLibraryUsageDescription | 说明用于选择图片 |
| 定位 | NSLocationWhenInUseUsageDescription | 说明用于展示附近内容 |
| 麦克风 | NSMicrophoneUsageDescription | 说明用于录音功能 |
iOS 17 之后,新提交的 App 还需要注意隐私清单 PrivacyInfo.xcprivacy。第三方 SDK 如果也声明了隐私信息,要连同 App 的隐私清单一起检查。这里不能当作可选项,因为审核和合规检查会逐步加强。第一次做隐私清单时,最容易漏掉的是第三方统计 SDK 和崩溃收集 SDK 对应的收集数据类型。
3.3 使用 xcodebuild 和 exportOptions.plist 构建归档
在 Xcode 图形界面中,菜单 Product -> Archive 可生成归档。但自动化团队更常使用命令行:
xcodebuild archive \ -workspace YourApp.xcworkspace \ -scheme YourAppScheme \ -configuration Release \ -archivePath build/YourApp.xcarchive归档完成后,导出 IPA 需要 exportOptions.plist。下面是一个通用配置示例:
<?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>method</key> <string>app-store-connect</string> <key>teamID</key> <string>YOUR_TEAM_ID</string> <key>signingStyle</key> <string>automatic</string> </dict> </plist>导出命令:
xcodebuild -exportArchive \ -archivePath build/YourApp.xcarchive \ -exportPath build/export \ -exportOptionsPlist exportOptions.plist注意:method 必须写成 app-store-connect,如果写成 ad-hoc,上传到 App Store Connect 时会被判为无效分发方式。exportOptions.plist 里的 teamID 要填开发者团队 ID,而不是 Apple ID。
3.4 用 altool 或 Transporter 上传构建
得到 IPA 后,可以用 Xcode 的 Organizer 上传,也可以用 Transporter 工具上传。命令行场景使用 altool:
xcrun altool --upload-app \ -f build/export/YourApp.ipa \ -t ios \ -u YOUR_APPLE_ID \ -p YOUR_APP_SPECIFIC_PASSWORDApple ID 开启双重认证后,应使用 App 专用密码,而不是登录密码。上传成功后会返回:
No errors uploading 'YourApp.ipa'如果返回错误,需要看错误码。常见有:
| 错误码/信息 | 含义 | 处理 |
|---|---|---|
| ERROR ITMS-90134 | 缺少或无效的证书 | 检查 Distribution 证书与描述文件 |
| ERROR ITMS-90022 | Bundle ID 不匹配 | 核对 App Store Connect 与工程 Bundle ID |
| ERROR ITMS-90683 | 缺少权限文件或 Info.plist 有误 | 检查 exportOptions、签名、entitlements |
| Invalid Bundle 错误 | 二进制结构或 SDK 版本问题 | 按要求重新打包,必要时升级 Xcode |
3.5 上传后必须验证构建状态
上传成功不代表 App Store Connect 已经拿到构建。等待几分钟后,在 App Store Connect 的“App 版本”页面,找到“构建版本”区域,点击加号,如果能看对应构建,说明处理完成。如果长时间没有出现,要确认:
- Xcode 版本是否过旧。
- 是否有 TestFlight 构建与 App Store 构建混淆。
- 网络中间层是否拦截了上传请求。
- 是否不小心选了“不包含符号”或“不上传符号”,导致后续崩溃日志不可用。
注意:上传以后不要马上在提交审核页面点击“提交审核”,先下载 TestFlight 构建做一遍真机回归。审核团队会因为启动崩溃直接拒绝 App,而这个问题完全可以在内测阶段发现。
4. App Store Connect 提审材料:决定是否进入“等待审核”
4.1 应用信息、审核信息与演示账号
App Store Connect 的“App 信息”页面需要填写名称、副标题、隐私政策 URL、类别、年龄分级等信息。“审核信息”页面的“演示账号”和“备注”对审核很重要。
如果 App 需要登录才能使用,必须在“备注”里提供可用的演示账号,否则审核员无法进入主界面,可能以“无法完成审核”或 2.1 拒绝。演示账号准备注意:
- 账号不能是刚注册后马上需要邮箱验证才能使用的,最好预置一个已激活账号。
- 账号要能覆盖核心功能,比如付费功能、核心业务流转。
- 备注里写清楚测试步骤,例如“打开 App 后使用账号 A 登录,进入首页点击扫描,然后能看到结果页”。
4.2 出口合规与加密声明
提交审核时要回答出口合规问题。如果 App 使用 HTTPS 的 TLS 加密,属于标准加密,通常可以声明为豁免。如果 App 包含自定义加密算法或额外加密组件,需要提供相关证明文件。这一步建议由团队确认后再填写,避免因错误声明导致审核流程停滞。
4.3 提审后状态变化与流程追踪
提交后状态一般会经过:等待审核、审核中、等待开发者发布或准备销售。
审核期间,可以打开 App Store Connect 的“App 审核”栏目查看回复。不要反复提交新的构建版本,因为会打断当前审核流程。如果审核团队提出了问题,通常会通过 Resolution Center 发送消息,需要先回复对方说明修改方案,再重新提交。
5. 审核被拒的常见原因与排查链路
5.1 从被拒邮件和 Resolution Center 拿到准确信息
审核被拒时,苹果会发邮件,同时在 App Store Connect 的 Resolution Center 里提供具体 Guideline。第一步是复制完整 Guideline 编号和审核备注,不要只凭印象去改。很多团队看到“Guideline 2.1”就以为只是审核慢,实际上 2.1 也可能指“信息不完整”或“需要提供更多说明”。
把问题归类:
- 元数据问题:截图、图标、描述、审核备注与功能不符。
- 功能问题:审核员无法登录、核心功能不可用、出现崩溃。
- 隐私问题:权限说明不清晰、采集数据未声明、第三方 SDK 未合规。
- 内容问题:包含审核不允许的内容。
- 技术问题:ITMS 错误、SDK 版本过旧、符号化缺失。
5.2 常见被拒代码速查表
| Guideline | 常见含义 | 处理建议 |
|---|---|---|
| 2.1 | App 完整性或信息不完整 | 提供演示账号、补充说明、回复 Resolution Center |
| 2.3.1 | 元数据不符合规范 | 修改描述、截图、隐藏开关或演示模式 |
| 2.5.1 | 使用了私有 API 或未声明能力 | 检查依赖 SDK,移除可疑代码 |
| 3.1.1 | 使用 App 内购买项目不当 | 确认数字内容购买是否接入 IAP |
| 4.2 | 功能过于简单或像网站壳 | 完善核心功能,重新描述价值 |
| 4.3 | 垃圾应用或重复应用 | 提供明显差异化功能或独立产品页面 |
| 5.1.1 | 数据收集与隐私问题 | 补充隐私政策,完善权限描述,检查数据用途 |
| 5.2 | 知识产权或商标问题 | 检查名称、素材、内容是否侵害第三方权益 |
5.3 审核期崩溃:优先用 TestFlight 和崩溃日志自测
审核期间崩溃会触发 2.1 或 2.2。排查时先看 App Store Connect 的“App 审核”消息里是否附带崩溃报告。如果有崩溃报告,下载并导入 Xcode 的 Organizer 或 Devices 窗口符号化:
xcrun symbolicatecrash -v \ crash.crash \ YourApp.app.dSYM符号化后,从堆栈里找到崩溃函数和调用链。常见崩溃原因:
- 权限弹窗未在 Info.plist 中声明。
- 弱网场景下请求回调未处理。
- 低版本系统调用了新 API。
- 第三方 SDK 在审核环境缺少某个服务。
5.4 隐私合规问题:权限目的字符串与第三方 SDK
如果审核被拒原因涉及 5.1.1 或隐私信息收集,需要把 App 内所有信息收集点列出来,与隐私政策逐条对应。注意第三方 SDK 也会产生数据收集,例如统计 SDK、崩溃收集 SDK、广告 SDK。检查方式可以在 Xcode 中观察启动时是否有 SDK 自动调用设备信息接口,或在隐私清单里补全声明。
iOS 17 之后,新提交的 App 还需要注意隐私清单 PrivacyInfo.xcprivacy。第三方 SDK 如果也声明了隐私信息,要连同 App 的隐私清单一起检查。这里不能当作“可选项”,因为审核和合规检查会逐步加强。第一次做隐私清单时,最容易漏掉的是第三方统计 SDK 和崩溃收集 SDK 对应的收集数据类型。
5.5 被拒后如何回复、修改并重启审核
被拒后不要立刻重新上传构建。正确流程:
- 阅读 Resolution Center 的完整消息。
- 复现问题:用演示账号、审核备注和截图模拟审核员的路径。
- 定位问题,修复后在本地和 TestFlight 验证。
- 回复 Resolution Center,说明修改内容、如何复现修复后的功能。
- 如果修改涉及新构建,上传新构建并重新选择构建版本。
- 再次提交审核。
回复要基于事实,不要只写“我们已修复”,要告诉审核员具体怎么验证。例如:“已使用演示账号 admin/demo123 登录,进入首页点击添加,功能可以正常弹出相机权限弹窗。”
6. 面向团队和自动化的提审最佳实践
6.1 用 Fastlane 实现测试、签名、上传、提审一体化
Fastlane 是常见的 iOS 自动化工具。基本流程可以配置成:
lane :beta do match(type: "appstore", readonly: true) build_app(scheme: "YourApp", export_method: "app-store-connect") upload_to_testflight end lane :release do match(type: "appstore", readonly: true) build_app(scheme: "YourApp", export_method: "app-store-connect") upload_to_app_store(skip_metadata: true, skip_screenshots: true) end要注意:match 可以同步证书和描述文件,但会让证书管理更集中,不适合单人小项目直接照搬。引入自动化脚本前,先确定团队签名责任人,避免多个人同时生成证书导致匹配混乱。
6.2 准备一份可执行的提审检查清单
每次提审前按清单检查:
- 开发者账号有效,团队角色有提交权限。
- Bundle ID 与后台 App ID 一致。
- 证书和描述文件均为 Active 且未过期。
- 版本号、构建号更新且构建号唯一。
- Info.plist 中的权限描述完整。
- 隐私政策 URL 可访问。
- 演示账号可用并覆盖核心流程。
- TestFlight 构建已做真机回归。
- 审核备注写清楚测试步骤。
- 截图、预览视频与当前功能一致。
- 数据库、服务端环境切换为正式或审核专用环境。
- 审核联系人邮箱可收到通知。
这个清单可以作为团队提审 SOP,谁提交谁负责过一遍。
6.3 正确处理审核周期、排期和加急
审核时间不是固定值。对于严重线上问题,可以在 App Store Connect 中申请加急审核,但要说明真实原因,例如线上版本出现崩溃或数据错误、用户无法使用核心功能。不要为了提前发布新功能去申请加急,那样不仅可能被拒绝,还容易影响后续审核节奏。
如果业务上必须制定发布排期,可以把“审核通过”作为第 T 天,往前推准备时间,而不是倒推“今天必须提交、明天必须上线”。
6.4 从一次性提审到持续交付
第二次、第三次提审时,真正的效率不是手速更快,而是把重复事项自动化:
- 版本号和构建号由脚本在发布分支自动递增。
- 权限描述和隐私清单纳入代码评审范围。
- TestFlight 外部测试提前 2 到 3 天观察崩溃和反馈。
- 提审备注与演示账号放在团队共享文档,避免每次重新填写。
这样每次提审都会像走一套固定的发布流程,而不是从头摸索。可以在 CI 中增加一个“提审前构建”任务,每次 push release 分支时自动执行 Archive 和上传,并把报告发送到团队群。这样可以减少“本地能过、CI 不过”的情况。
处理 iOS 应用提审,最重要的并不是“会点提交审核”,而是理解从证书、构建到材料、被拒回复的整条链路。实际项目中,建议把 TestFlight 内测作为正式提审的前置门槛,把隐私和权限清单作为代码评审的一部分,把被拒问题记录成团队排查手册。这样每一次提审都能更快定位问题、更准确回复审核团队,也更能避免同一个原因反复被拒。