一个月前的线上崩溃需要排查,你却发现 Xcode Cloud 的构建产物已经无法下载。
最快解法:不要把 Xcode Cloud 当作长期制品仓库。截至 2026 年 8 月 30 日,Apple 官方说明构建信息与产物最多可访问 30 天;偶尔发版可以当天手动归档,持续发布的项目应在构建完成后用 App Store Connect API 自动下载,并在独立存储或常驻远程 Mac 上做恢复验收。(developer.apple.com)
这篇文章适合依赖 Xcode Cloud 发布 App、却从未单独保存构建产物的独立开发者;也适合需要长期保留符号信息、回溯崩溃报告的小团队。如果你希望集中检查和恢复 xcarchive、xcresult,下面这条时间线可以直接照着执行。
先把 30 天窗口换成归档时间表
Xcode Cloud 的问题不在于它不能生成制品,而在于它的默认访问窗口不适合作为长期证据库。Apple 官方列出的构建产物包括详细构建日志、导出的 App Archive、App 二进制文件、Framework,以及测试结果包;这些信息和产物从构建完成时起最多可访问 30 天。(developer.apple.com)
因此,你不需要把每一次普通分支构建都永久保存,而应先按发布价值分类:
| 产物类别 | 建议处理 | 主要用途 |
|---|---|---|
正式发布版本的 xcarchive |
必须外部归档 | 重新检查签名、版本、构建环境和崩溃关联 |
dSYM 与其它符号信息 |
必须与对应构建绑定保存 | 将崩溃报告中的地址转换为函数名和行号 |
| 构建日志 | 正式发布建议保存 | 复盘签名、依赖、脚本和上传问题 |
xcresult 测试结果包 |
按项目风险保存 | 查看测试失败、截图、覆盖率和测试日志 |
| 普通分支构建 | 按需保存 | 失败复现、代码审查或短期回归验证 |
Apple 明确要求你保留每个已分发 App 的 Xcode Archive,因为缺少对应 Archive,后续可能无法利用崩溃报告完成诊断。发布版的符号文件也不能只按文件名保存:每个二进制文件可能对应独立的 dSYM,二进制与符号文件通过 Build UUID 关联。(developer.apple.com)
⚠️ “构建已经上传到 App Store Connect”不等于“你的历史归档已经安全保存”。App Store Connect 中的构建记录、Xcode Cloud 工作流产物、你自行上传的附加文件,以及本地导出的
xcarchive,应当分别记录来源。
构建完成当天建立归档基线
第一次备份不要直接写一个“下载成功”脚本。先人工完成一份完整归档,确认你真正需要哪些文件,再把这份结果作为自动化的对照基线。
推荐使用这样的目录结构:
AppName/
1.8.0/
build-184/
archive/
symbols/
test-results/
logs/
manifest.json
checksums.txt
目录名应至少绑定以下信息:
- App 名称或稳定的内部标识;
- App 版本号;
- Build Number;
- Xcode Cloud 工作流名称;
- 构建 ID;
- 构建完成时间;
- 构建结果状态;
- 下载文件名、文件类型和校验值。
Xcode Cloud 的 API 模型不是“给我一个压缩包”这么简单。一个 Build Run 可以关联多个 Build Actions,而每个 Build Action 又可能关联 Artifacts、问题和测试结果。Apple 的文档将构建运行、构建动作和产物拆成不同资源,因此归档脚本也应沿这个关系定位文件,而不是只根据最新构建名称猜测下载对象。(developer.apple.com)
人工基线至少要完成以下动作:
- 在 Xcode 或 App Store Connect 中打开目标构建详情。
- 确认它属于正确的 App、工作流、分支或标签。
- 下载 Archive、符号信息、测试结果和构建日志。
- 解压 Archive,检查是否存在预期的
.xcarchive结构。 - 记录 Build Number、构建 ID、文件名和文件大小。
- 为每个文件生成校验记录,并把清单与文件放在同一归档目录。
Apple 官方也说明,Artifacts 资源可提供文件类型、文件名、文件大小和下载地址;单个构建动作的 Artifacts 列表接口最多返回 200 个资源。这里的 200 是接口分页上限,不是你应该保存的文件数量,脚本仍应处理分页和空结果。(developer.apple.com)
第一周接入 App Store Connect API
当你已经完成一份人工基线,第二阶段才是让 App Store Connect API 接管重复下载。API 使用 JSON Web Token 进行授权,相关密钥应在 App Store Connect 的团队账户中创建和管理。(developer.apple.com)
自动化流程可以拆成下面这条链:
读取成功构建
→ 定位 Build Run
→ 获取 Actions
→ 筛选 Archive / 发布相关 Action
→ 获取 Artifacts
→ 下载文件
→ 写入 manifest
→ 生成校验记录
→ 标记成功或告警
脚本中的变量必须使用占位符,不要把真实凭据提交到仓库:
ASC_KEY_ID=<你的_API_Key_ID>
ASC_ISSUER_ID=<你的_Issuer_ID>
ASC_PRIVATE_KEY_PATH=<本地私钥路径>
APP_ID=<你的_App_ID>
WORKFLOW_ID=<你的_Workflow_ID>
这几类权限不要混在一起:
| 凭据或权限 | 用途 | 不应承担的职责 |
|---|---|---|
| App Store Connect API 凭据 | 查询构建、动作、产物和测试结果 | 不能替代代码签名资产 |
| 代码签名证书与私钥 | 签名和发布 | 不应放入普通制品归档目录 |
| Provisioning Profile | 指定签名与能力配置 | 不等于完整发布归档 |
| 远程 Mac 登录权限 | 执行解压、读取、符号化和恢复脚本 | 不应直接暴露 API 私钥 |
查询层面,先读取 Build Run 的状态、构建编号、工作流和完成时间,再读取关联的 Actions。只有当构建结果成功,并且 Action 类型符合你的发布规则时,才进入 Artifacts 下载阶段。这样可以避免把失败构建、Pull Request 构建或普通测试构建误归档为正式版本。
下载完成后,脚本必须把执行结果写入状态文件,例如:
{
"buildId": "<BUILD_ID>",
"workflow": "<WORKFLOW_NAME>",
"status": "success",
"artifacts": [
{
"fileName": "<FILE_NAME>",
"fileType": "<FILE_TYPE>",
"downloaded": true,
"checksum": "<CHECKSUM>"
}
]
}
如果同一个构建再次触发任务,脚本应先读取 buildId 和文件校验记录。已成功归档的对象直接跳过,只有下载中断、校验失败或清单不完整时才重试。这就是幂等;没有幂等控制,定时任务很容易生成大量重复文件。
构建事件与定时扫描的取舍
Xcode Cloud webhook 可以在构建创建、开始和完成时向 HTTPS 端点发送请求;请求中包含 App、工作流、构建和代码仓库等信息。Webhook 的作用是通知外部任务,不是替你保存制品,因此收到事件后仍要调用 App Store Connect API 查询 Artifacts 并执行下载。(developer.apple.com)
你可以按发布频率选择触发方式:
- 低频发版:使用定时任务扫描最近完成的构建,部署简单,适合每周或每月才发布一次的独立项目。
- 高频发版:使用
BUILD_COMPLETED事件触发归档,减少等待窗口。 - 关键发布流程:事件触发后仍保留补偿扫描,避免网络故障、接口超时或任务进程退出造成漏归档。
Webhook 处理器要先验证请求来源和签名,再从事件中提取构建 ID。Apple 的 webhook 文档指出,如果服务端返回可重试的错误,或在 30 秒 内没有返回响应,Xcode Cloud 会重新发送请求。你的处理器不应在 HTTP 请求内同步执行大文件下载,更稳妥的做法是快速返回成功,将构建 ID 放入后台队列,再由归档任务完成下载。(developer.apple.com)
事件处理还要记录三种状态:
received:已收到事件
processing:已开始查询和下载
archived:文件已下载并通过验收
如果下载失败,状态应明确写成 failed,并带上错误原因、重试次数和下一次扫描时间。不要只记录“任务运行过”,因为任务运行成功不代表制品已经完整落盘。
FAQ:保留窗口、下载与恢复
见元数据中的 FAQ。下面再补充一个容易被忽略的判断:App Store Connect 中的构建存在,不代表你仍然拥有可用于诊断的完整 Archive 和符号信息。正式版本的恢复目标应是“可以重新定位、读取、分析”,而不是“网页上还能看到一个构建编号”。
发版验收不止检查下载成功
正式发布时,建议把验收拆成“文件层、版本层、分析层”三组。Apple 的 Artifacts 文档将 App Archive、测试结果包和构建日志都视为构建动作产生的文件输出;测试结果包还可以包含会话结果、代码覆盖率和其它日志。(developer.apple.com)
文件层
- 压缩包可以完整下载并解压;
- Archive 目录结构没有损坏;
dSYM文件没有被清理或重命名到无法追踪;xcresult能被 Xcode 打开;- 构建日志文件可以读取;
- 校验值与
checksums.txt一致。
版本层
- App 名称或 Bundle Identifier 对应正确项目;
- Version 与 Build Number 对应 App Store Connect 中的目标构建;
- 工作流、分支或标签信息能够回溯;
- 构建状态确实是成功,而不是仅仅完成了上传;
- 发布产物和测试产物没有被不同 Build Number 混放。
分析层
- 用归档中的符号信息检查一次脱敏崩溃报告;
- 在 Xcode 中打开
xcresult,确认测试会话和失败信息可读取; - 检查 Archive 中的应用、扩展和 Framework 是否都有对应符号;
- 验证旧产物能够按 App、版本和 Build Number 被重新定位。
Apple 的测试文档说明,使用 xcodebuild 执行测试时会生成 .xcresult 测试结果包,其中可以包含会话结果、代码覆盖率和其它日志;这个文件可以在 Xcode 中打开或分享。(developer.apple.com)
✅ 恢复验收的最低标准不是“文件存在”,而是“你能用它解释一次历史失败”。如果 Archive 能解压,却找不到对应 Build Number 或符号 UUID,这份备份仍然不合格。
长期运行的清理与恢复规则
不同类型的构建不应使用一套固定保存期限。你可以根据发布风险制定内部规则:
- 内部测试构建:优先保留最近一段开发周期内仍可能复现问题的版本;
- 候选发布构建:保留到正式版本稳定、审核和上线风险关闭;
- 正式发布构建:至少保留到项目维护和崩溃排查不再需要它;
- 失败构建:如果包含关键回归证据,单独标记,不要被普通清理规则误删。
存储职责也应分层:
- 本地磁盘:适合短期缓存和快速下载,不适合作为唯一副本;
- 对象存储或备份盘:适合保存压缩包、清单、校验记录和日志;
- 常驻远程 Mac:适合定期打开 Xcode Archive、读取
xcresult、运行符号化脚本和执行恢复演练。
如果你需要一个可持续运行的 Mac 环境来检查历史制品,可以先了解 VMSPIN 的远程 Mac 使用方式,再判断是采用短期租赁节点,还是为持续发布任务保留常驻环境。对于只是偶尔发版的项目,手动下载加独立存储通常已经足够;对于频繁发布、需要自动符号化或多人共享恢复环境的团队,常驻远程 Mac 更容易把检查脚本固定下来。
每月一次的恢复抽查
- [ ] 随机选择一个历史正式 Build Number;
- [ ] 从独立存储下载对应归档;
- [ ] 校验
checksums.txt; - [ ] 解压并读取
xcarchive; - [ ] 检查 App、扩展和 Framework 的符号文件;
- [ ] 用脱敏崩溃报告执行一次符号化;
- [ ] 打开对应
xcresult,确认测试结果可读; - [ ] 把恢复结果、耗时和失败原因写入审计记录;
- [ ] 检查自动任务最近一次运行状态,而不是只看文件数量。
如果归档任务还需要定期用 Xcode 打开历史产物、执行符号化或持续运行下载脚本,你可以进一步查看 VMSPIN 的 Mac 租赁方案,再根据发布频率选择短期环境或长期节点。
当前方案与 Mac 归档方案的最后判断
只依赖 Xcode Cloud 本身,优点是接入快、无需维护主机;但它有三个现实缺点:构建信息和产物访问窗口只有官方确认的 30 天,Webhook 只负责通知而不是存储,恢复操作还依赖你提前保留 Archive 和符号信息。只把构建编号留在 App Store Connect,也无法替代一份经过校验、能够被 Xcode 重新读取的外部归档。
如果你只是偶尔发布,构建完成当天手动下载即可;如果你持续发版,App Store Connect API 加独立存储是更稳妥的基础方案;如果还要长期执行 Xcode 读取、符号化、测试结果检查和恢复脚本,租赁 VMSPIN 的真实 Mac 环境会比临时找一台个人电脑更适合形成稳定流程。