某次构建用了相近的总时长,真正的慢点却可能完全不同:一次卡在依赖安装,另一次耗在设备测试矩阵。Xcode Cloud 构建太慢时,不要先迁移:本周先用构建报告拆分队列、环境准备、编译、测试和归档,再按持续出现的瓶颈决定优化、迁移远程 Mac,或采用双轨 CI。
01谁适合看这篇文章
如果你在使用 Xcode Cloud,构建时间不断增加,却还不知道慢在哪里,这篇文章适合你。
如果你的 CI 需要安装复杂依赖、保留缓存、访问内网或运行常驻服务,也可以用本文的判断条件评估远程 Mac。
研发负责人则可以据此决定:继续消耗 Xcode Cloud 使用量、迁移部分任务,还是让两套环境分别承担不同职责。
02先把“慢”拆成五段,而不是看一个总时长
Xcode Cloud 的一次工作流可能包含克隆代码、解析依赖、运行自定义脚本、执行 xcodebuild、测试、归档和上传产物。Apple 文档明确说明,工作流动作会在临时构建环境中完成,而且不同动作可能分别创建环境、解析依赖并执行脚本。(Apple 工作流动作文档)
所以你要先建立一份同一提交的耗时记录:
- 队列等待:构建提交后多久才开始执行。
- 环境准备:克隆仓库、解析依赖和安装工具花了多久。
- 实际编译:
build、archive或build-for-testing的执行时间。 - 测试阶段:单元测试、UI 测试和不同设备组合分别耗时多少。
- 产物处理:签名、归档、上传和通知是否形成尾部等待。
建议选择同一个提交、同一个 Scheme 和同一组测试范围,分别保存一条正常样本和一条异常样本。不要把不同提交、不同设备矩阵、不同工作流的总时长放在一起比较。
App Store Connect 可以查看构建数量、构建时长和使用趋势,并支持导出使用数据;但使用时间和用户看到的构建完成时间并不完全相同,因为 Xcode Cloud 会并行处理部分任务。(Apple 使用数据文档)
03⚠️ 经验判断:如果你只有流水线总时长,没有动作日志和使用数据,就还不能证明 Xcode Cloud 本身变慢。先补证据,再改工作流。
第一步:处理反复出现的依赖准备
每次都重新安装依赖,通常不是“编译器性能不足”,而是工作流把环境准备成本重复支付了。
Xcode Cloud 使用临时构建环境。项目需要的第三方工具、私有依赖和脚本资源,必须在工作流开始时重新获得或安装。Swift Package 依赖应提交 Package.resolved,否则依赖解析可能偏离你本地验证过的版本。(Apple 依赖管理文档)
你可以按下面顺序检查:
Package.resolved是否已提交,且没有被忽略。- 私有 Git 仓库和内部框架是否已经授权。
ci_post_clone.sh是否每次都安装相同工具。- 脚本是否把下载、解压、生成代码和依赖解析混在一起。
- 是否在每个动作中重复执行同一套初始化命令。
- 依赖下载失败后,脚本是否进行了无上限重试。
自定义脚本可以放在 ci_scripts 目录中,常见阶段包括克隆后、xcodebuild 前和 xcodebuild 后。Apple 也要求脚本正确返回非零退出码,否则失败可能被推迟到后续步骤才暴露。(Apple 自定义脚本文档)
优化动作与停止条件
先把脚本改成可观察的形式。每个重要步骤输出开始和结束标记,记录依赖解析、工具安装、代码生成和上传的耗时。不要把令牌、密钥或完整认证信息写入日志;被标记为 secret 的环境变量会在构建日志中隐藏具体值。
然后做两次对照:
- 一次使用完整初始化流程。
- 一次只执行必要的构建准备。
如果删除非必要安装后,构建恢复稳定,继续留在 Xcode Cloud 更合理。如果每次都必须启动本机服务、访问内网、保留大体积生成物,或者需要管理员权限,迁移相关任务到远程 Mac 的理由就开始成立。Apple 文档说明,自定义脚本不能通过 sudo 获得管理员权限。(Apple 依赖管理文档)
第二步:分清缓存收益与环境残留
“缓存没生效”不是一个足够精确的结论。
Derived Data、Swift Package 依赖、代码生成目录、测试产物和自定义脚本生成文件,属于不同类型的状态。即使某部分缓存被复用,也不代表整个动作可以跳过依赖解析或重新生成步骤。
你需要分别观察三类结果:
- 全新构建:用于确认项目能否从干净环境复现。
- 依赖复用后的构建:用于判断锁文件和依赖获取是否稳定。
- 小范围代码变更后的增量构建:用于判断日常反馈是否真的变快。
不建议为了追求一次更短的时间,保留无法解释的环境残留。缓存如果不能被验证、清理和重建,后续失败会变得更难定位。
对专业团队来说,缓存的价值不只是“少下载一次”。你还要记录它是否导致以下问题:
- 依赖版本与锁文件不一致。
- 生成文件来自旧提交。
- 测试偶尔读取错误的资源。
- 清理后构建结果无法复现。
- 新成员或新分支无法在相同环境完成构建。
如果你需要的是可复现发布,干净构建仍应保留在某个工作流中。Xcode Cloud 支持配置不同工作流和动作,你可以把日常快速验证与完整验证拆开,而不是让每次提交都执行最重的流程。
05第三步:测试矩阵慢时,先拆职责再减设备
Xcode Cloud 的测试动作不只是“运行测试”。测试阶段还可能包含测试构建、多个平台、多个 Scheme 和多组设备配置。Apple 建议将快速、短时测试与更全面、长时间测试放到不同工作流中。(Apple 工作流动作文档)
判断顺序建议如下:
- 拉取请求工作流只保留能快速发现编译错误和核心回归的测试。
- 主分支工作流增加完整单元测试和关键 UI 测试。
- 定时工作流再执行更广泛的设备和系统组合。
- 发布工作流负责归档、签名和分发,不要混入所有实验性测试。
- 对无效旧提交启用自动取消,避免过时构建继续占用执行资源。
Xcode Cloud 的 Auto-cancel Builds 可用于同一工作流的新构建排队场景。开启后,旧提交可能在新提交到来时被取消,从而减少“每个提交都排队等完整测试”的浪费。(Apple Xcode Cloud 工作流参考)
设备减少还是工作流拆分
如果所有测试都在同一动作中串联,先拆工作流。这样你可以保留发布前的完整矩阵,同时让日常提交只运行小范围验证。
如果工作流已经拆开,但快速验证仍然被设备组合拖慢,再减少快速工作流中的设备类型。不要一开始就删除边缘系统测试,因为那会把问题从“反馈慢”变成“覆盖不足”。
以下情况更适合设计远程 Mac 测试池:
- 测试需要固定的模拟器状态。
- 多个任务要共享预置测试资产。
- 测试脚本需要访问内网服务。
- 团队需要自行控制节点并发。
- 测试任务之间存在长期复用的环境状态。
这不是把所有工作流搬走,而是让 Xcode Cloud 负责标准验证,让远程 Mac 承担需要主机控制权的测试任务。
06第四步:检查脚本和网络依赖造成的长尾等待
Xcode Cloud 提供预定义环境变量,包括当前工作流、Scheme、构建动作和构建标识。临时环境还会提供标准的 HTTP_PROXY 和 HTTPS_PROXY 变量,网络工具是否正确读取这些变量,会直接影响下载和上传行为。(Apple 环境变量文档)
对自定义脚本重点检查:
- 下载命令是否设置合理的失败处理。
- 重试是否只针对可恢复的网络错误。
- 上传前是否先检查文件是否存在。
- 外部 API 调用是否有明确退出码。
- 日志是否能区分 DNS、认证、下载和服务器响应。
- 失败后是否会继续执行无意义的后续步骤。
一个常见问题是脚本没有失败,但也没有真正完成任务。例如下载命令等待网络,外部服务没有响应,脚本继续进入下一步,最后才在归档或上传阶段失败。这样的日志看起来像“Xcode 构建太慢”,实际慢点在构建之外。
如果任务需要长期后台进程、内网白名单、固定 IP、物理设备或跨构建保留文件,临时环境通常不是理想载体。此时远程 Mac 的价值不在于宣传中的某个峰值性能,而在于你能控制主机、进程、文件系统和恢复流程。
07常见问题:从日志到迁移判断
依赖为什么会在不同构建中反复安装?
因为 Xcode Cloud 的构建环境是临时的,不能直接等同于你本地长期运行的开发机。你需要检查锁文件、私有依赖权限和 post-clone 脚本,确认哪些工具真的必须每次安装。若初始化步骤无法压缩,且任务还依赖持久缓存或本机服务,再考虑迁移。
在哪里查看一次构建的详细耗时?
先在 App Store Connect 打开对应 App 的 Xcode Cloud 页面,查看构建报告、动作日志和使用数据。使用数据可以帮助你观察构建数量、构建时长和趋势,但不要用它替代动作级日志。总时长必须拆成队列、准备、编译、测试和归档后再分析。
测试矩阵变慢时,应该先减设备还是拆工作流?
先拆分职责,再减少设备。拉取请求适合快速验证,主分支适合回归,定时任务适合完整矩阵。如果不同测试之间共享环境、并发冲突明显,继续减少设备未必解决问题,应该拆成独立工作流或交给可控的远程 Mac 测试节点。
哪些信号说明任务适合迁到远程 Mac?
当持续瓶颈来自重复初始化、内网连接、常驻服务、持久缓存或主机权限不足时,才进入迁移评估。先迁移最慢的一类任务,使用同一提交和同一 Scheme 对照运行,确认构建复现、日志完整、重启恢复和维护投入,再决定是否扩大范围。
08用同一项目做三种方案判断
不要把“继续优化”和“迁移远程 Mac”理解成二选一。对大多数团队,更可靠的方式是先按任务拆分,再决定哪些动作留在 Xcode Cloud。
| 方案 | 更适合的任务 | 主要优点 | 主要限制 | 判断信号 |
|---|---|---|---|---|
| 继续使用 Xcode Cloud | 标准编译、基础测试、TestFlight 发布验证 | 工作流集成度高,配置入口集中,适合标准化任务 | 临时环境不适合长期保留复杂状态 | 依赖可锁定,脚本短,测试范围可分层 |
| 迁移到远程 Mac | 常驻服务、复杂初始化、内网测试、专用脚本 | 可控制主机、文件、进程和恢复流程 | 需要自行维护 macOS、权限、网络和 CI 节点 | 日志显示准备和环境限制长期占主要时间 |
| 双轨运行 | 标准构建加复杂测试或专用发布流程 | 保留 Xcode Cloud 的标准验证,同时获得主机控制权 | 两套环境需要统一版本和结果对照 | 发布顺畅,但某类测试或构建任务持续受限 |
迁移时不要先复制整条流水线。优先选择一个边界清晰的任务,例如完整 UI 测试、依赖初始化复杂的归档,或必须访问内网的验证步骤。等它在远程 Mac 上完成稳定复现,再决定是否扩大范围。
如果你需要评估远程节点的登录、权限、Xcode 安装、重启和构建恢复,可以先参考 远程 Mac 租赁开发环境验收。若重点是长期 CI 并发,再结合 Mac CI/CD 节点方案 做节点数量和任务拆分。
| 检查项 | 继续优化 Xcode Cloud | 迁移远程 Mac | 双轨 CI |
|---|---|---|---|
| 依赖是否能锁定 | ✅ 能 | ✅ 能 | ✅ 两边都能 |
| 是否需要常驻服务 | ❌ 不适合 | ✅ 适合 | ✅ 复杂任务放远程 Mac |
| 是否需要内网访问 | ⚠️ 先验证代理与权限 | ✅ 可按网络方案设计 | ✅ 标准任务留在 Xcode Cloud |
| 是否需要主机级权限 | ⚠️ 受临时环境限制 | ✅ 可控范围更大 | ✅ 按任务拆分 |
| 是否需要完整测试矩阵 | ✅ 可用不同工作流分层 | ✅ 可自建测试池 | ✅ 快速测试与复杂测试分开 |
| 是否要降低维护量 | ✅ 更省节点维护 | ❌ 需要维护主机 | ⚠️ 维护两套环境 |
| 最适合的下一步 | 优化日志、缓存和触发条件 | 先做短周期对照 | 先迁移单一瓶颈任务 |
本周建议的 5 步执行顺序
- 选定一个提交和一个 Scheme,保存一次正常构建与一次异常构建。
- 从构建报告提取阶段耗时,至少区分准备、编译、测试和归档。
- 检查依赖与脚本,确认是否重复安装工具、重复解析依赖或等待外部服务。
- 重做触发策略,把快速验证、主分支回归和完整测试拆开,并检查自动取消设置。
- 只迁移一个瓶颈任务,在远程 Mac 上验证构建复现、日志完整、重启恢复和维护成本。
不要设置一个未经官方确认的“超过多少分钟就必须迁移”的硬阈值。Apple 官方资料确认了临时环境、工作流动作、构建报告、缓存、自定义脚本和自动取消等机制,但没有给所有项目统一适用的“过慢”标准。
如果你现在的方案只是继续增加 Xcode Cloud 工作流,却没有减少重复初始化、无效测试触发和长尾网络等待,计算用量会继续被低价值步骤消耗。反过来,直接把全部任务搬到自建节点,也会增加 macOS 更新、权限、签名、网络和故障恢复的维护负担。
当日志已经证明主要时间花在临时环境重复准备、持久服务缺失或主机控制权不足时,短周期租赁一台远程 Mac 做对照,通常比立刻全面迁移更稳妥。VpsMesh 提供真实 Mac 主机的远程访问和 root 权限,适合先验证复杂任务是否能稳定复现,再决定局部迁移还是长期双轨;你可以从 VpsMesh 的远程 Mac 方案开始核对适合的节点与周期。