一次完整的交付链路
把自己写的 App 装到别人手机上,中间要跨过四道关:工程要能构建、代码要能验证、构建产物要能签名、上架材料要过审核。很多人第一次提交卡在第三道和第四道关上,原因往往不是代码写错,而是对整条链路缺乏整体认知,于是在某个环节反复试错。
上架不是开发的最后一步,而是工程能力的一次体检:签名、测试、隐私清单,任何一项含糊,都会在审核环节被打回。
- 开发期工程结构 + 依赖管理 + 本地调试,重点是能稳定构建。
- 验证期单元测试与 UI 测试跑在 CI 上,防止回归。
- 签名期证书、描述文件、Entitlements 三者匹配,产出自签名归档包。
- 发布期建版本 → 上传 → TestFlight 内测 → 提交审核 → 灰度发布。
工程结构与依赖管理
一个可维护的 iOS 工程按「功能模块 + 基础设施」切分,而不是按文件类型堆在一起。常见约定是:App/ 放入口与依赖装配,Features/ 按业务域建立子目录,Core/ 放网络、存储、工具等公共能力,Resources/ 放资源与本地化文件。这样切分的好处是删除功能时可以整目录移除。
依赖管理首选 Swift Package Manager,它已经集成进 Xcode,版本锁定、分支依赖与二进制目标都能表达。老项目里的 CocoaPods 可以继续用,但新项目除非依赖只提供 Pod,否则不建议再引入——多一套构建系统就多一份排查成本。
- 一个功能一个目录界面、模型、服务放一起,删功能时整目录移除。
- 依赖方向单向Features 可依赖 Core,Core 绝不反向依赖。
- 环境配置分离用 xcconfig 区分 Debug / Release / Staging。
签名、证书与描述文件
iOS 的签名机制本质是一条信任链:开发者证书证明「你是谁」,App ID 定义「哪个应用」,描述文件把二者与设备列表、权限(Entitlements)绑定在一起。三者任意一项对不上,Xcode 就会报签名错误。日常开发建议交给自动签名:在 Signing & Capabilities 里勾选自动管理并选定 Team,Xcode 会负责申请证书与生成描述文件。
持续集成环境需要在命令行完成同样的动作,下面是归档与导出安装包的标准写法:
# 产出一个用于分发的归档包
xcodebuild archive -project Demo.xcodeproj -scheme Demo \
-configuration Release -destination "generic/platform=iOS" \
-archivePath build/Demo.xcarchive
# 用 ExportOptions.plist 导出 ipa(method 可选 app-store / ad-hoc)
xcodebuild -exportArchive -archivePath build/Demo.xcarchive \
-exportOptionsPlist ExportOptions.plist -exportPath build/ipa
把 ExportOptions.plist 纳入版本管理,写清 method、teamID 与签名方式,可以让「本机能打包、流水线打不了」这类问题彻底消失,也让发布流程可以被完整复现。
测试:单元测试与 UI 测试
iOS 的测试分两层:XCTest 跑逻辑,速度快、定位准;XCUITest 跑真实界面交互,慢但能覆盖集成路径。合理的比例是业务逻辑全部有单元测试,关键用户旅程各留一条 UI 测试。测试不是为了覆盖率数字,而是为了在你改动公共代码时能立刻知道有没有踩坏别处。
import XCTest
@testable import Demo
final class CartTests: XCTestCase {
func testAddIncreasesTotal() {
let cart = CartModel()
cart.add("SwiftUI 实战")
cart.add("并发编程")
XCTAssertEqual(cart.items.count, 2)
XCTAssertEqual(cart.total, 20)
}
}
@testable import 让我们能访问模块内部类型,但它只在 Debug 配置下生效,要注意别让测试辅助代码泄漏到发布版本。异步逻辑用 async 测试方法配合 await,不必再用基于期望值(XCTestExpectation)的老写法,后者既啰嗦又容易在超时上出问题。
打包、TestFlight 与上架审核
归档成功后可以用 Xcode 的 Organizer 面板上传,也可以走命令行。上传完成后版本会先出现在 TestFlight 中,此时可以邀请内测人员验证,而不必先通过审核。这是把「开发完成」和「发布上线」解耦的关键一步。
提交审核前要准备的材料包括:应用描述与关键词、多尺寸截图、隐私清单(PrivacyInfo.xcprivacy,声明数据收集类型与用途)、以及审核备注。如果登录是必需步骤,务必在其中提供可用的测试账号,否则审核人员无法验证功能,大概率被拒。
- 权限文案相机、定位、麦克风必须说明具体场景,不能写「为了更好的体验」。
- 账号删除提供注册的 App 必须同时提供应用内删除账号入口。
- 隐私标签商店里的隐私标签要与实际采集行为、隐私清单三者一致。
- 无占位内容审核期间不能崩溃,也不能出现「敬请期待」这类空页面。
提交后常见状态是等待审核、正在审核、被拒与可供销售。被拒时不要急着改代码再提交一次,先在 Resolution Center 里按条回应,沟通效率通常比盲目重试高得多。
被拒的常见原因与应对
按发生频率排序,前几类分别是:元数据问题(截图或描述与实际功能不符)、隐私合规(缺少隐私清单或声明不一致)、崩溃与性能问题、账号与支付规则。元数据类问题改起来最快,往往只需在 App Store Connect 里修改文案即可重新提交。
隐私相关的问题最容易被忽略。请在项目里加入 PrivacyInfo.xcprivacy,逐项声明使用的 API 类别与收集的数据类型;如果你的 SDK 依赖也采集数据,要一并在文档中声明,否则会被判定为声明不一致,这种拒绝理由通常需要改动代码或更换依赖才能解决。
崩溃类问题建议先在本地复现审核环境的配置:干净安装、首次启动、拒绝所有权限后启动、断网启动。这几个场景覆盖了绝大多数被拒的崩溃来源,提前自查能省下一轮往返。模拟器上用 xcrun simctl 卸载并重装安装包,就能模拟一次全新安装的首次启动路径。
发布之后:监控与迭代
上架只是开始。发布后至少要把三件事接上:崩溃收集、启动与帧率指标、版本分布数据。没有这些数据,你只能靠用户反馈猜测线上质量,而愿意反馈的用户永远是极少数。
- 用 Organizer 的 Crashes 面板看符号化后的堆栈,先修崩溃率最高的那一类。
- 用 MetricKit 采集启动耗时与卡顿,把「感觉有点慢」变成可比较的数字。
- 灰度阶段先放一成流量,观察一天再全量,变更说明写清楚。
想补齐其他平台的知识,可以继续阅读本栏目里的《SwiftUI 声明式 UI:从零构建界面》与《macOS 桌面应用开发入门》,两篇共用同一套 Swift 技术栈。