首页 / 博客 / Tuist Xcode Cache 值得开吗?2026 远程 Mac 判断
ENGINEERING_BLOG · 2026.09.15

Tuist Xcode Cache 值得开吗?2026 远程 Mac 判断

症状:同一项目反复编译很慢,但打开缓存后流水线仍然没有明显变快。
最快解法:重复编译占主导就先试用 Tuist Xcode Cache;等待空闲节点、UI 测试或签名任务占主导就扩容远程 Mac;两类问题并存时采用缓存与扩容双轨。

这篇文章适合大型 iOS / macOS 项目的开发者、维护自托管 Mac CI 的 DevOps 工程师,以及需要决定采购、租用还是优化构建节点的研发平台负责人。

最后更新于 2026 年 9 月 15 日。本文涉及的 Tuist Xcode Cache 行为、Xcode 构建计时方式、缓存传输机制和 Runner 路由规则,已根据 Tuist Xcode Cache 官方指南Tuist 变更记录Apple Xcode 构建优化文档CI Runner 官方规则 复核。

SECTION 01先看时间分布,而不是先开缓存

Tuist Xcode Cache 最适合解决的是“相同输入反复触发相同编译工作”。它不是通用的流水线加速按钮,也不会自动消除依赖解析、脚本阶段、Simulator 启动、UI 测试、归档或签名等待。

第一步应在 Xcode 中使用 Build With Timing Summary,或在命令行执行:

xcodebuild \
  -workspace App.xcworkspace \
  -scheme App \
  -showBuildTimingSummary \
  build

Apple 建议在优化前先收集每个构建任务的耗时,并区分首次冷构建与后续增量构建。相关文档还提醒,脚本阶段如果缺少正确的输入和输出声明,可能在每次构建时重复执行。

你需要分别记录以下五类时间:

  • 编译时间:Swift、Objective-C、模块和依赖目标真正执行编译的时间。
  • 缓存传输时间:从缓存服务下载、上传、校验和重组产物的时间。
  • Runner 排队时间:任务进入 CI 后,到获得可用 Mac 的等待时间。
  • 测试执行时间:Simulator、单元测试、UI 测试或设备测试真正运行的时间。
  • 有效交付时间:从提交进入流水线,到产物、测试结果或发布包可以被使用的总时间。

哪些构建阶段最适合交给 Tuist Xcode Cache?
它主要针对可由 Xcode 构建系统识别、且输入保持一致的编译产物。对于同一提交反复编译、多个 CI 节点编译相同依赖,或者开发者在本地与 CI 之间重复构建的场景,缓存更可能产生价值。Tuist 官方文档确认,该功能需要 Xcode 26.0 或更高版本,并通过远程缓存共享编译产物。

如果耗时主要出现在依赖解析、代码生成、Shell 脚本、测试启动或签名阶段,直接开启缓存通常不会改变主要瓶颈。此时应先修复项目配置或调整流水线分层,而不是用缓存命中率解释所有慢构建。

进入缓存试验的条件

满足下面多数条件时,可以进入小范围缓存试验:

  • 同一项目存在大量重复的增量构建或干净构建。
  • Build Timing Summary 显示编译任务占据主要时间。
  • 同一工具链、架构、配置和依赖版本在不同节点之间保持一致。
  • 项目没有频繁改变编译参数、模块路径或生成目录。
  • CI 节点能够稳定访问缓存端点,且身份认证不会随机失效。

反过来,如果每次构建都更换 Xcode、SDK、目标架构、编译宏或依赖锁定文件,缓存输入会不断变化。你此时看到的“命中率低”,可能不是缓存服务无效,而是项目本身没有提供稳定的可复用输入。

SECTION 02缓存命中与项目输入

缓存已经启用,却没有看到预期收益时,先不要直接停用。你需要把“本地命中、远程命中、未命中和缓存未参与”分开记录。

在 CI 中检查以下项目:

  1. Xcode 版本与 SDK:不同工具链会改变编译输入,不能只看提交哈希。
  2. 目标架构:Apple Silicon 节点、Intel 节点、设备 SDK 和 Simulator SDK 可能产生不同产物。
  3. 编译配置:Debug、Release、测试专用配置和发布配置不能混为一组。
  4. 编译参数:宏定义、优化级别、Swift 编译器选项和模块验证选项都可能改变缓存键。
  5. 路径与环境变量:绝对路径、生成目录、临时目录和脚本读取的环境变量可能使两个看似相同的任务实际不同。
  6. CI 身份认证:服务端缓存需要有效凭据;Tuist 官方 CI 指南建议使用项目级令牌,并把 TUIST_CONFIG_TOKEN 作为 CI Secret 保存。Tuist CI 认证说明

命中不稳定时,什么情况下仍可保留缓存?
如果低命中只发生在少量边缘任务,但稳定命中的依赖编译已经减少了重复工作,可以保留缓存,同时限制上传范围并继续修复输入一致性。若连续多次对照测试都显示远程命中少、传输时间高于节省的编译时间,并且项目短期内无法统一工具链,则应暂停缓存试验,把资源转向项目配置或节点调度。

你可以用下面的可勾选清单做一次核对:

  • [ ] 同一提交在相同 Xcode、SDK、架构和配置下重复运行。
  • [ ] 日志能区分本地命中、远程命中、未命中和缓存未参与。
  • [ ] CI 中的 TUIST_CONFIG_TOKEN 已配置为 Secret,且认证失败不会被静默忽略。
  • [ ] 缓存上传与下载时间单独记录,没有并入编译时间。
  • [ ] 至少对比一次无缓存冷构建、缓存预热构建和缓存复用构建。
  • [ ] 对照任务没有同时改变依赖锁定文件、生成项目或运行环境。
  • [ ] 设定停止条件:连续多轮命中不稳定或传输成本持续超过编译收益时回退。

SECTION 03传输成本与远程节点位置

缓存命中不等于流水线一定更快。若编译产物很大、节点到缓存端点的网络路径较远,缓存可能只是把 CPU 等待转换成网络等待。

Tuist 在 2026 年 9 月 8 日 的变更记录中说明,Xcode Cache 新增了基于内容的分块复用:上传时只发送服务端缺少的分块,下载时复用本机保留的已验证分块。官方给出的局部基准显示,修改一个 C 函数时,对象传输负载减少 47.4%;重命名大型 Swift 模块中的一个属性时,传输负载减少 27.8%。但这两个数字衡量的是传输字节,不是完整构建时间,不能直接外推成普遍的构建提速。

因此你应在日志中单独观察:

  • 缓存请求建立和认证耗时;
  • 产物上传耗时;
  • 产物下载和校验耗时;
  • 分块复用后的实际传输量;
  • 缓存命中后仍需执行的本地编译任务。

面对慢构建,如何在缓存和 Runner 扩容之间做第一步选择?
如果单个任务已经获得 Runner 后仍长时间编译,先做缓存对照;如果任务很快完成编译,却长时间等待空闲 Mac,先扩容或调整队列;如果编译和排队都占据明显比例,就不要二选一,应让缓存减少单次执行时间,同时增加远程 Mac 节点降低等待。

远程 Mac 的地域也会影响判断。节点和缓存端点不在相近网络路径时,上传与下载可能成为新的长尾。你可以先限制缓存上传到高复用目标,再比较不同节点地域的“编译加传输总时长”,而不是只看缓存命中事件。

MACNOX 的远程 Mac 节点选择入口适合用来准备临时对照节点,但正式决策前仍应以你自己的项目、工具链和缓存端点测试结果为准。

SECTION 04Runner 排队与任务路由

远程 Mac CI 排队和编译慢,必须从时间戳上区分。最简单的做法是在 CI 日志中记录四个时间点:

  1. 工作流创建时间;
  2. Job 进入队列时间;
  3. Runner 接收任务时间;
  4. xcodebuild 真正开始时间。

如果第 2 到第 3 个时间点间隔很长,问题在 Runner 容量、标签、分组或节点在线状态,不在 Tuist Xcode Cache。以 GitHub Actions 自托管 Runner 为例,任务只有在找到同时满足 runs-on 标签和分组条件、并且在线空闲的 Runner 后才会被分配;匹配不到时会持续排队。

标签设计也会造成“明明有 Mac,任务却拿不到”的假象。你应为构建、测试和发布节点设置明确标签,例如:

  • macos:操作系统;
  • arm64:处理器架构;
  • xcode-26:工具链版本;
  • buildtestrelease:任务池用途。

如果 PR 构建在编译开始前就长时间等待,或者发布任务长期独占节点,扩容通常比继续调缓存更直接。缓存只能缩短部分执行阶段,不能让一个已经被占用的 Runner 同时接收第二个任务,也不能把不匹配的节点变成可用节点。

你可以把流水线拆成 PR 构建池、定时测试池和发布池,再观察每个池的排队时间。若新增节点上线后仍因标签不匹配、工具链不一致或缓存端点网络问题无法接收任务,先修复路由和环境,不要继续盲目增加节点。

如果需要估算长期并发容量,可以先阅读 MACNOX 的远程 Mac 方案与节点选择页面,再用实际队列记录校正,而不要仅根据团队人数或提交次数猜测节点数量。

SECTION 05UI 测试、归档与签名边界

缓存对 UI 测试、归档和签名任务的帮助边界在哪里?
缓存可以复用部分编译产物,但 UI 测试仍然需要启动测试目标、Simulator 或图形会话,并实际执行测试步骤。Apple 文档明确指出,Simulator 不能完全代表真实设备的性能或硬件特性;需要验证真实设备行为时,仍应使用物理设备。Apple Simulator 与物理设备测试说明

测试结果本身也会产生 .xcresults 包,其中包含会话结果、覆盖率和日志。测试执行时间、测试运行器启动时间和失败重试时间,都不能简单归入编译缓存收益。

签名与归档则有另一层限制。归档必须使用合适的真实设备或仅构建设备目的地,Simulator SDK 生成的应用不能直接用于提交到 App Store;证书、Provisioning Profile、Keychain 和发布凭据也必须位于可控的发布环境中。Apple 归档问题排查说明

因此建议把节点分成三类:

  • 构建池:优先追求编译复用和高并发,适合启用 Tuist Xcode Cache。
  • 测试池:保留稳定的 Simulator、测试运行器和图形会话,避免被大量普通编译占用。
  • 发布池:隔离签名证书、Keychain、归档和发布凭据,只接受受控分支或人工批准任务。

SECTION 06对照试运行与三选一决策

不要用一次构建的快慢决定是否长期启用缓存。选择同一提交、同一工具链、同一 Runner 标签和同一任务范围,至少准备三组对照:

  • 无缓存组:清理 DerivedData 和相关缓存后运行;
  • 缓存复用组:先完成一次预热,再运行相同任务;
  • 扩容模拟组:使用空闲远程 Mac 节点,记录排队和执行时间。

每组都要记录冷构建、缓存命中、缓存传输、Runner 排队、测试执行和有效交付时间。不要只记录 CI 页面显示的总时长,因为总时长无法告诉你收益来自缓存、节点空闲,还是测试任务偶然变短。

选择“优先启用 Tuist Xcode Cache”

  • 编译时间是主要耗时;
  • 同一依赖和源码经常重复编译;
  • 远程命中稳定;
  • 缓存传输没有抵消编译节省;
  • Runner 排队处于可接受范围。

停止条件是:输入持续变化、命中状态无法解释,或多轮测试都显示缓存传输高于节省的编译时间。

选择“增加或拆分远程 Mac 节点”

  • 排队时间明显高于实际编译时间;
  • UI 测试、归档或签名任务长期独占 Runner;
  • 任务需要不同 Xcode、架构、Simulator 或凭据环境;
  • 通过标签和分组无法解决资源冲突。

停止条件是:新增节点上线后仍因标签不匹配、工具链不一致或缓存端点网络问题无法接收任务。此时应先修复路由和环境,而不是继续增加节点。

选择“缓存与扩容双轨”

  • 重复编译确实占据较长时间;
  • 同时存在明显 Runner 排队;
  • PR、定时测试和发布任务互相抢占;
  • 缓存能够稳定复用,但无法解决 UI 测试和签名执行时间。

这是大型项目最常见、也最容易被误判的情况:缓存解决单个任务过慢,扩容解决任务拿不到节点,两者作用在不同时间段,不能互相替代。

如果你当前使用的是单台 Mac mini 作为长期构建节点,常见缺点是并发能力有限、发布任务容易阻塞普通构建,而且设备维护、系统升级和重启恢复都需要你自己承担。纯云端 Linux 方案又无法直接提供完整的 Xcode、Simulator 和签名环境。完成对照测试后,如果主要时间消耗来自排队、Simulator 或发布任务,可以再通过 MACNOX 的远程 Mac 方案准备临时节点,先验证拆分构建池是否有效,再决定是否长期租用。

真正适合租赁的场景,是你需要临时扩充 Mac CI、验证新的 Xcode 工具链,或在发布高峰期增加并发;如果你的任务长期满载、需要固定物理设备或必须接入专用硬件,自购 Mac 或自建固定节点可能更合适。