Xcode 26 编译太慢:2026 远程 Mac 怎么加速?

Xcode 26 编译太慢:2026 远程 Mac 怎么加速?

Xcode 官方文档将首次完整构建、增量构建和脚本阶段分别处理;这意味着 Xcode 26 编译太慢时,第一步不是清空 DerivedData,也不是立刻升级 Mac,而是先保存构建计时,确认时间究竟花在源码、依赖、脚本、链接、测试还是硬件等待上。只有 CPU、内存或磁盘持续成为主要瓶颈,才值得扩容远程 Mac 或拆分构建任务。(Apple:提升增量构建速度)

这篇文章适合每天多次执行增量编译、希望缩短代码修改到运行反馈时间的独立开发者;也适合需要在远程 Mac 上运行 Release Archive、自动测试或持续集成任务的维护者。准备增加 Mac 配置、但还无法确定问题是否真的来自硬件不足的小团队,也可以用文中的验收方法先做一次对照。

构建计时:先区分不同的慢

同一个项目显示“构建耗时很长”,可能对应完全不同的原因。首次构建需要准备完整产物,增量构建依赖已有缓存,Release Archive 还会加入链接、签名、符号处理和发布脚本,测试则包含模拟器启动与测试执行。

因此,基线至少要分成四类:

工作负载 固定条件 重点观察 容易误判的因素
首次构建 固定代码提交、Scheme、配置和目标设备 依赖准备、完整源码编译、资源处理 首次下载不等于日常编译能力
增量构建 每次只修改一个已知文件 被重新编译的文件、Target、脚本 清理缓存后无法代表正常开发
Release Archive 固定 Release 配置和归档目标 编译、链接、签名、符号处理、脚本 不能只看 Archive 总时间
测试任务 固定 Test Plan 和模拟器 应用编译、模拟器启动、测试执行 测试等待不全是编译耗时

在 Xcode 中,可通过 Product 菜单执行带计时摘要的构建,然后在 Report navigator 查看构建记录。命令行则可以加入 -showBuildTimingSummary

xcodebuild \
  -workspace "<WORKSPACE_PATH>" \
  -scheme "<SCHEME_NAME>" \
  -configuration Debug \
  -destination 'platform=iOS Simulator,name=<SIMULATOR_NAME>' \
  -showBuildTimingSummary \
  build

输出示例:

Build Timing Summary

CompileSwiftSources        <TIME_A>
PhaseScriptExecution        <TIME_B>
Ld                           <TIME_C>
ProcessInfoPlistFile         <TIME_D>
CodeSign                     <TIME_E>

项目名、路径、Scheme、模拟器名称和耗时均为占位符。需要保存的是任务排序与阶段分布,而不是拿一组脱离项目的时间当作通用标准。Apple 的增量构建说明也建议关注 preparation、具体 Target 和单项构建任务。

经验: 每次测试都固定提交、Scheme、Build Configuration、目标设备和依赖状态。否则,首次下载 Package、切换模拟器或改变编译配置,都可能被错误地归入“Xcode 编译变慢”。

增量开发:先修复项目侧重复工作

日常开发最重要的指标,是修改一个文件后能否快速重新运行。若一个小改动触发多个无关 Target、资源处理或代码生成任务,更换 Mac 只能让这些无效任务执行得稍快,不能减少它们本身。

Target 依赖与构建顺序

打开目标的 Build Phases 和 Target Dependencies,逐项确认依赖关系:

  • 应用 Target 是否错误依赖测试 Target。
  • 多个 Framework 是否被串成不必要的单一路径。
  • 自定义脚本生成的文件是否被正确声明为输入。
  • Scheme 的 Build Order 是否允许无依赖任务并行。
  • 同一模块是否因不同编译选项而重复生成。

Apple 的构建系统说明指出,没有相互依赖的任务可以尽可能并行;不准确的依赖关系会限制构建系统的调度空间。(Apple:配置 Xcode Target)

如果计时摘要显示 preparation 或某个 Target 占据主要时间,先检查项目结构。此时升级芯片通常不是第一动作。

Swift 源码与模块边界

某个 Swift 文件长期明显慢于同一 Target 中的其他文件,通常值得单独检查。常见原因包括复杂泛型推断、过长表达式、过度组合的协议、巨大的类型声明,以及不必要的跨模块符号暴露。

Apple 的代码实践文档建议减少不必要的导出符号,并让类型信息更明确,以帮助编译器处理源码。(Apple:通过代码实践改善构建效率)

验证时采用单变量对照:

提交 A:结构调整前,只修改一个目标文件
提交 B:只完成一项代码拆分或类型声明调整
固定条件:相同 Scheme、配置、设备和缓存状态
比较内容:单文件耗时、Target 耗时、增量构建总时长

不能用一次干净构建和一次增量构建比较,也不能把单次偶然波动直接归因于代码优化。

自定义脚本与 DerivedData

Build Phases 中的 Run Script 是常见隐性成本。若脚本没有准确声明输入文件和输出文件,Xcode 可能在每次构建中重复执行;脚本还可能与资源处理或代码生成形成串行关系。(Apple:增量构建优化)

检查以下内容:

  • 输入文件是否完整。
  • 输出文件是否真实存在。
  • 输出路径是否稳定。
  • 脚本是否每次都下载相同内容。
  • 是否将网络下载、代码生成和资源复制混在一个阶段。
  • 输出文件未变化时,脚本是否仍然执行完整流程。

DerivedData 只适合排查缓存损坏、索引异常或旧产物冲突。清理之后,下一次构建往往需要重新生成更多中间产物;如果根因是脚本、Target 依赖或依赖解析,清理不会改变任务图,也不会让后续构建持续变快。

Release Archive:拆分发布链路

Debug 增量构建很快,不代表 Release Archive 也快。Release 配置可能启用不同的优化、符号处理、资源处理、签名和导出脚本。Apple 将 Archive 作为后续验证和分发的构建产物处理,因此不能用普通 Debug 构建时间替代发布基线。(Apple:分发 Beta 与正式版本)

xcodebuild \
  -workspace "<WORKSPACE_PATH>" \
  -scheme "<SCHEME_NAME>" \
  -configuration Release \
  -destination 'generic/platform=iOS' \
  -archivePath "<ARCHIVE_PATH>" \
  -showBuildTimingSummary \
  archive

建议按照阶段记录:

阶段 需要确认的内容 常见原因 优先动作
依赖准备 Package 解析、下载和认证 锁文件缺失、私有仓库访问失败 固定依赖并检查权限
源码编译 Swift、Objective-C、代码生成 文件复杂、模块边界不合理 查看单文件和 Target 计时
链接 Ld、库合并、符号处理 Target 过多、配置差异 对比 Debug 与 Release 设置
资源处理 Asset、Info.plist、生成资源 重复复制、脚本无输出声明 修正输入输出关系
签名导出 CodeSign、Export 证书、Profile、导出脚本 先检查配置和权限

如果只有 Release 变慢,优先比较 Debug 与 Release 的 Build Settings、额外 Target、符号处理和发布脚本。若计时摘要显示编译并不突出,而签名或脚本占用大部分时间,扩容 CPU 不会解决主要问题。

依赖恢复:把网络等待和源码编译分开

新远程 Mac、持续集成节点或新租期首次构建,常见额外耗时来自仓库同步、Swift Package 下载、二进制依赖恢复、私有仓库认证和网络访问。它们都可能出现在“构建很慢”的表象下。

Apple 的持续集成文档要求将 Package.resolved 提交到仓库,以便构建环境使用固定的依赖版本;直接调用 xcodebuild 时,还可以使用 -disableAutomaticPackageResolution。(Apple:Swift Package 持续集成)

检查文件与命令:

<PROJECT_NAME>.xcodeproj/
└── project.xcworkspace/
    └── xcshareddata/
        └── swiftpm/
            └── Package.resolved
xcodebuild \
  -workspace "<WORKSPACE_PATH>" \
  -scheme "<SCHEME_NAME>" \
  -configuration Release \
  -disableAutomaticPackageResolution \
  -showBuildTimingSummary \
  build

排查顺序应为:

  1. 当前检出的提交是否包含 Package.resolved
  2. 构建节点是否每次重新解析 Package。
  3. 问题属于解析失败、下载缓慢,还是依赖源码编译缓慢。
  4. 私有依赖是否需要 SSH 凭据、令牌或特定网络权限。
  5. 二进制依赖是否因缓存缺失而重复下载。
  6. 依赖下载完成后,慢点是否已经转移到源码编译。

需要长期运行 iOS 构建任务时,可先查看 远程 Mac 使用入口,再用固定提交完成本地与远程对照。远程 Mac 能解决环境常驻和开发设备占用问题,但不能替代依赖输入的可复现管理。

模拟器测试:并发需要用数据调节

测试总时间至少包含应用编译、模拟器启动、测试执行和失败重试。模拟器也不能完全代表物理设备的性能与硬件特征,因此测试结果不能全部归因于 Xcode 编译。(Apple:在模拟器或实体设备运行 App)

并行测试会增加测试进程、模拟器实例、CPU 和内存压力。资源充足时可能减少等待;资源接近上限时,启动和调度成本会抵消并行收益。可通过命令行设置并行工作进程:

xcodebuild \
  -workspace "<WORKSPACE_PATH>" \
  -scheme "<SCHEME_NAME>" \
  -testPlan "<TEST_PLAN_NAME>" \
  -destination 'platform=iOS Simulator,name=<SIMULATOR_NAME>' \
  -parallel-testing-worker-count <WORKER_COUNT> \
  test

建议拆成两套测试策略:

  • 快速反馈:只运行受影响模块的单元测试,保持较低并发。
  • 发布验证:运行完整 Unit、Integration 和 UI 测试,逐级比较并发设置。
  • 合并或夜间任务:保存完整日志、测试结果和构建计时,避免开发者每次修改都等待全量流程。

Apple 的测试组织文档支持通过不同 Test Plan 管理开发测试与完整验证,从而减少不必要的反馈等待。(Apple:组织测试以改善反馈)

远程 Mac:按真实工作负载验收

远程 Mac 不应被当作“编译慢”的默认答案。完成项目侧修复、固定依赖输入并调节测试并发后,才适合把同一项目迁移到远程环境做对照。

验收步骤如下:

  1. 检出与本地相同的代码提交。
  2. 核对 Xcode、macOS、Scheme、配置和目标设备。
  3. 保留工作区与构建缓存,执行增量构建。
  4. 清理指定项目产物后,执行一次干净 Archive。
  5. 执行快速反馈测试。
  6. 执行完整 Test Plan。
  7. 重复关键任务,检查耗时是否稳定。
  8. 同时观察 CPU、内存压力、磁盘等待、依赖下载和远程会话。

Xcode 26 的具体行为可能随小版本更新而变化。涉及版本要求、已修复问题或系统兼容性时,应核对对应的官方 Release Notes,而不是套用社区经验。(Apple:Xcode 26 Release Notes)

配置判断表

观察结果 更可能的根因 优先动作 扩容判断
单个 Swift 文件长期突出 源码结构或表达式复杂 拆分代码并减少不必要符号 暂不扩容
每次出现相同脚本 输入输出未声明 修正脚本依赖与执行条件 暂不扩容
依赖阶段占主要时间 解析、下载或认证 固定锁文件并检查访问权限 暂不扩容
CPU 长时间饱和且源码排队 编译吞吐受限 检查并行度,再比较芯片 可考虑升级芯片
内存压力明显、模拟器等待 并发过高或内存不足 降低并发并复测 可考虑增加内存
磁盘等待突出 产物、空间或读写瓶颈 检查缓存目录和磁盘状态 复测后决定
构建正常但远程画面卡顿 VNC 或网络交互问题 将交互调试与后台构建分离 不一定需要升级

若本地 Mac 长期被 Archive 和测试占用,远程 Mac 可以承担常驻构建、自动测试和发布前验证。若只是偶发一次慢构建,租用或升级硬件未必划算。

构建验收清单

  • [ ] 使用同一代码提交、Scheme、配置和目标设备建立基线。
  • [ ] 分别记录首次构建、增量构建、Release Archive 和测试任务。
  • [ ] 保存 Build Timing Summary 或 xcodebuild 输出。
  • [ ] 检查 Target Dependencies 和 Build Order。
  • [ ] 为 Run Script 填写准确的输入文件与输出文件。
  • [ ] 确认 Package.resolved 已提交并与当前提交匹配。
  • [ ] 区分依赖下载时间与源码编译时间。
  • [ ] 将快速反馈测试和完整发布测试拆成不同 Test Plan。
  • [ ] 逐级调整测试并发,比较总时长与失败重试成本。
  • [ ] 在远程 Mac 上重复增量构建、干净 Archive 和测试。
  • [ ] 同时记录 CPU、内存压力、磁盘等待和网络下载。
  • [ ] 只有硬件资源持续成为主瓶颈时,才扩容或拆分任务。

如果需要按周或按月完成真实项目验收,可参考 Mac 远程租赁价格说明,并坚持使用同一项目、同一提交和同一清单。这样得到的结论,才与实际工作负载有关。

常见问题

构建报告怎样显示单个任务的时间?

在 Xcode 中执行带 Timing Summary 的构建,随后打开 Report navigator 查看阶段明细。命令行构建则加入 -showBuildTimingSummary。应重点观察 preparation、Target、单个 Swift 文件、Run Script 和链接阶段,并把结果与相同条件下的另一轮构建比较。

清理构建缓存后,为什么下一轮仍然很慢?

清理 DerivedData 会移除已有构建产物和部分缓存,下一轮需要重新生成更多内容。它适合排查缓存损坏或旧索引冲突,不会改变脚本、Target 依赖和 Package 解析逻辑。若慢点来自项目结构,频繁清理反而会持续丢失增量构建收益。

远程环境应该优先增加内存还是换更快的芯片?

先根据计时摘要和系统负载判断。源码编译任务长、CPU 长时间饱和,更偏向芯片或编译并行度;内存压力、交换空间和模拟器并发造成等待,则优先考虑内存。依赖下载、仓库同步和串行脚本占主要时间时,两者都不是第一解决方案。

如何避免 Swift Package 在每次构建时重新解析?

Package.resolved 与项目提交到仓库,构建时使用固定提交,并在适合的持续集成命令中加入 -disableAutomaticPackageResolution。私有依赖还要核对凭据、访问权限和网络路径。解析失败、下载慢和源码编译慢必须分开记录,不能统一归因于 Xcode。

提高测试并发后,为什么总时间反而增加?

并发测试会同时消耗更多 CPU、内存和模拟器资源。当工作进程超过远程 Mac 或本地设备的承载能力时,模拟器启动、任务调度和失败重试会增加。应测试多个并发档位,并同时记录总完成时间、失败率和重试成本,而不是只追求更高的并发数量。

如果当前做法是把增量编译、Archive 和测试全部压在一台本地 Mac 上,长期会遇到设备被构建占用、模拟器与编译任务争用内存、构建无法在开发者离线时继续运行等问题。直接购买专用 Mac,则会增加一次性硬件投入和闲置维护成本。完成计时诊断后,用同一项目在 SFTPMAC 的远程 Mac 上跑一轮增量构建、Archive 和测试,更适合判断瓶颈究竟来自项目还是资源上限;若本地设备确实长期被构建任务占用,再选择按周或按月租用,而不是在定位问题前盲目升级。