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
排查顺序应为:
- 当前检出的提交是否包含
Package.resolved。 - 构建节点是否每次重新解析 Package。
- 问题属于解析失败、下载缓慢,还是依赖源码编译缓慢。
- 私有依赖是否需要 SSH 凭据、令牌或特定网络权限。
- 二进制依赖是否因缓存缺失而重复下载。
- 依赖下载完成后,慢点是否已经转移到源码编译。
需要长期运行 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 不应被当作“编译慢”的默认答案。完成项目侧修复、固定依赖输入并调节测试并发后,才适合把同一项目迁移到远程环境做对照。
验收步骤如下:
- 检出与本地相同的代码提交。
- 核对 Xcode、macOS、Scheme、配置和目标设备。
- 保留工作区与构建缓存,执行增量构建。
- 清理指定项目产物后,执行一次干净 Archive。
- 执行快速反馈测试。
- 执行完整 Test Plan。
- 重复关键任务,检查耗时是否稳定。
- 同时观察 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 和测试,更适合判断瓶颈究竟来自项目还是资源上限;若本地设备确实长期被构建任务占用,再选择按周或按月租用,而不是在定位问题前盲目升级。