iOS 打包、证书与 TestFlight
iOS 发布的门槛不在 Flutter,而在 Apple 的签名体系:证书、App ID、描述文件三者必须匹配,错一环就报“签名不匹配”。好消息是这一套一旦配通,后面每次发版只是改版本号。本章按“准备环境 → 理解签名 → Xcode 配置 → 出包上传 → TestFlight 内测”的顺序讲清,最后给没有 Mac 的团队一条替代路径。
硬性前提
| 前置条件 | 用途 | 常见坑 |
|---|---|---|
| macOS | 编译与签名只能在 Mac 上完成 | 虚拟机不稳定,CI 用 macOS Runner |
| Xcode | 编译 iOS 工程,版本要匹配 Flutter 要求 | 版本过旧时 flutter doctor 会提示 |
| CocoaPods | 管理 iOS 原生依赖 | 未安装时 pod install 失败,见下方排查 |
| Apple Developer 账号 | 发布必须付费账号(个人/公司 99 美元/年) | 免费账号只能真机调试,不能上架 |
flutter doctor -v # 先确认 Xcode 与 CocoaPods 两项都是绿勾
sudo gem install cocoapods # 未安装时;也可用 brew install cocoapods
pod repo update # 依赖索引过旧时报找不到 pod 时执行
证书、App ID 与描述文件
| 概念 | 作用 | 谁来创建 |
|---|---|---|
| Development 证书 | 真机调试签名 | Xcode 自动管理即可 |
| Distribution 证书 | 上架与 TestFlight 签名 | Xcode 自动或手动在开发者后台生成 |
| App ID | 与 Bundle Identifier 一一对应的唯一标识 | 开发者后台自动注册 |
| Provisioning Profile | 把证书、App ID、设备绑在一起的授权文件 | 自动签名由 Xcode 维护 |
结论:团队规模不大时一律用 Xcode 的自动签名(Automatically manage signing),手动管理只在有多环境、多团队协作、需要精细控制时才值得。App Store Connect 里创建的 App 记录,其 Bundle ID 必须与工程里完全一致,且一旦提交首个版本就不能再改。
Xcode 配置
open ios/Runner.xcworkspace # 必须打开 xcworkspace,打开 Runner.xcodeproj 会丢失 Pods 依赖
在 Xcode 里依次检查:
Runnertarget →General→Identity:Bundle Identifier用反向域名(如com.example.shop),与 App Store Connect 记录一致。Signing & Capabilities:勾选Automatically manage signing,选择 Team,确认签名证书与描述文件没有红色报错。Build Settings→Versioning:Version对应pubspec.yaml的版本号前缀,Build对应+后的构建号,Flutter 构建时会自动同步。Info→App Icons and Launch Images:用flutter_launcher_icons与flutter_native_splash生成,不要手改资源目录。
出包与上传
flutter build ipa --release # 产出 build/ios/ipa/*.ipa 与 .xcarchive
flutter build ipa --release --export-method app-store # 明确导出用途
flutter build ipa --release --dart-define=ENV=prod # 注入环境变量
flutter build ipa 内部会执行 pod install、编译并打包,产出的 .xcarchive 可直接用于上传。上传有三条路:
| 方式 | 操作 | 适用 |
|---|---|---|
| Xcode | Product > Archive 后 Distribute App | 本地调试签名问题最直观 |
| Transporter | 拖入 .ipa,一键上传 | 只需上传、不改配置 |
| 命令行 | xcrun altool / xcrun notarytool 系工具 | CI 自动化 |
上传后到 App Store Connect 的 TestFlight 页签等待处理完成,通常几分钟到半小时;首次上架还需要补全应用信息、隐私问卷与年龄分级。
TestFlight 内测流程
- 内部测试:添加团队成员(最多 100 人),无需审核,构建处理完成后立即可测。
- 外部测试:最多 10000 人,首个构建需要苹果审核(通常 1~2 天),适合公开灰度。
- 导出合规:
Info.plist里配置ITSAppUsesNonExemptEncryption可免去每次上传都要回答加密问题。 - 构建号必须递增:上传重复的
Build号会被直接拒绝,CI 里用时间戳或自增号生成。 - 测试反馈集中在 TestFlight 应用内的截屏与崩溃记录里,比微信群收集截图高效得多。
权限文案与隐私清单
权限文案写不好是最常见的审核退回原因,文案必须说明“用来做什么”,而不是“需要这个权限”。
<!-- ios/Runner/Info.plist -->
<key>NSCameraUsageDescription</key>
<string>用于拍摄商品照片并上传评价</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>用于从相册选择头像</string>
<key>NSUserTrackingUsageDescription</key>
<string>用于向你展示更相关的推荐内容(可拒绝)</string>
PrivacyInfo.xcprivacy 是新版要求的隐私清单文件,声明应用使用的 API 类别与数据采集类型(如 NSPrivacyAccessedAPICategoryUserDefaults、NSPrivacyCollectedDataTypes)。第三方 SDK 不提供该文件时会收到 ITMS-91053 警告,需在构建里补齐或替换该 SDK。App Store Connect 的“App 隐私”问卷必须与实际采集行为一致,收集了设备标识或位置就要如实勾选。
常见报错与排查
| 报错 | 原因 | 处理 |
|---|---|---|
CocoaPods not installed | 未装 CocoaPods | sudo gem install cocoapods 后重跑 |
pod install 卡住或失败 | 依赖索引过旧、网络受限 | pod repo update,或清理 Pods/ 与 Podfile.lock 重装 |
Signing for "Runner" requires a development team | 未选 Team | Xcode 里选择 Team 并启用自动签名 |
No profiles for 'com.x.y' were found | Bundle ID 与后台记录不一致 | 核对标识符,或让 Xcode 重新生成描述文件 |
Provisioning profile doesn't include signing certificate | 证书与描述文件不匹配 | 删除旧证书,重新自动签名 |
Invalid Bundle. ... does not support the minimum OS Version | 依赖库最低版本高于工程设置 | 提高 IPHONEOS_DEPLOYMENT_TARGET |
ITMS-91053: Missing API declaration | 缺隐私清单声明 | 补 PrivacyInfo.xcprivacy 或升级相关 SDK |
排查顺序固定:先在 Xcode 里确认签名区块无红色,再 flutter clean 后重新 pod install,最后才怀疑证书本身。
没有 Mac 怎么办
| 方案 | 说明 | 代价 |
|---|---|---|
| 云 Mac(MacinCloud 等) | 远程桌面操作 Xcode | 按小时计费,网络延迟影响体验 |
| CI 云构建(Codemagic、GitHub Actions macOS Runner) | 在云端自动打包并上传 TestFlight | 需要把证书与密钥托管为 CI Secrets |
| 外包打包 | 把源码交给有 Mac 的团队出包 | 源码安全与迭代速度都受影响 |
对没有 Mac 的团队,最实际的组合是:Android 本地发版,iOS 用 macOS Runner 自动构建 + TestFlight 分发,日常 iOS 调试借助同事的 Mac 或云 Mac 完成。
提交前自检
| 检查项 | 要求 |
|---|---|
| Bundle Identifier | 与 App Store Connect 记录完全一致,首版后不可更改 |
| 版本号与构建号 | pubspec.yaml 的 version: 1.2.0+45,构建号每次上传必须递增 |
| 图标与启动图 | 无透明通道、无圆角(系统会自动裁切),用生成工具产出 |
| 权限文案 | 每条权限都有说明用途的中文文案 |
| 隐私 | PrivacyInfo.xcprivacy 与 App 隐私问卷一致 |
| 账号与演示数据 | 需登录的应用必须提供可用的测试账号 |
| 出口合规 | ITSAppUsesNonExemptEncryption 已配置 |
小结:iOS 发布前先确认 macOS + Xcode + CocoaPods 三件套,用 Xcode 自动签名维护证书、App ID 与描述文件,open ios/Runner.xcworkspace 里核对 Bundle Identifier 与 Team,然后 flutter build ipa 出包经 Xcode 或 Transporter 上传 TestFlight;权限文案与 PrivacyInfo.xcprivacy 要如实声明,遇到签名类报错按“Xcode 签名区块 → clean + pod install → 证书”三步排查,没有 Mac 就用 macOS CI Runner 代替。