软件设计

[模块化 #6] 用 Tuist 轻松实现 iOS 模块化

如果团队沿着本系列把模块扩展到几十个,就会遇到一种新的疲惫感。

4 分钟阅读
[模块化 #6] 用 Tuist 轻松实现 iOS 模块化 封面图

如果团队沿着本系列把模块扩展到几十个,就会遇到一种新的疲惫感。

每增加一个模块就要重复配置,项目文件冲突依然存在,而且不同成员的模块配置还会有细微差异。

Tuist 正是为了解决这个问题。简单来说,它把 Xcode 项目文件从仓库中移除,改用 Swift 代码声明并生成项目。

本文将总结 Tuist 解决的问题、Project.swift 的基本用法,以及在大型团队中发挥价值的二进制缓存。基于 2026 年 7 月的 Tuist 4。


Tuist 解决了哪些问题?

核心是把 pbxproj 的所有权从人转移给工具。

在常见的 iOS 项目中,project.pbxproj 会提交到仓库。添加文件、修改 target 设置都会改动它,而它的格式也难以阅读,因此出现合并冲突时很难处理。

在 Tuist 项目中不会提交这个文件,而是提交名为 Project.swift 的声明。

对比 普通项目 Tuist 项目
仓库中保存的内容 project.pbxproj Project.swift(Swift 代码)
项目文件 手动管理 每次通过 tuist generate 生成
合并冲突 pbxproj 中频繁发生 Swift 代码 diff 中很少发生
添加模块 GUI 操作 + 重复配置 调用一行函数

项目文件变成生成产物后,就不再是合并冲突的目标。将它加入 .gitignore 即可。


Project.swift 是什么样的?

关键在于模块定义是 Swift 代码,因此可以把重复配置封装成函数。

// Project.swift
let project = Project(
    name: "MyApp",
    targets: [
        .target(
            name: "MyApp",
            destinations: .iOS,
            product: .app,
            bundleId: "com.example.myapp",
            sources: ["Sources/**"],
            dependencies: [
                .target(name: "FeatureSearch"),
                .target(name: "CoreNetwork"),
            ]
        ),
    ]
)

到这里,它看起来可能和 SPM(Swift Package Manager)的 Package.swift 很像。区别在于可扩展性。

将团队标准的模块形式定义为辅助函数后,新增功能模块真的只需一行代码。

// 团队标准:功能模块 = 源码 + 测试 + 演示应用套件
let searchModule = Target.featureModule(name: "Search")
let orderModule = Target.featureModule(name: "Order")
// 每个模块包含若干 target,配置在辅助函数中统一 3

如果有 30 个模块,使用 pbxproj 就要重复操作 30 次设置界面;使用 Tuist 则只需向数组中添加名称。成员之间配置不一致的可能性也随之消失。

内置依赖图可视化功能。只需执行一次 tuist graph,就能绘制模块依赖关系,方便直观看到循环依赖,或上一篇文章中警告过的“所有人都依赖的臃肿模块”。

从声明生成项目的流程
从声明生成项目的流程

二进制缓存:构建速度的下一步

上一篇文章提到,模块化可以缩小重新编译的范围;Tuist 缓存则更进一步。

tuist cache 会预先构建每个模块,并将其保存为二进制文件(framework)。之后执行 tuist generate 时,未发生变化的模块会用缓存二进制替代源码。

  • 如果我只在开发搜索功能:只以源码形式打开搜索模块,其余几十个模块都以构建完成的二进制形式接收。
  • 即使是全量构建,实际上也只剩下“我的模块 + 链接”。

使用远程缓存后,团队和 CI 可以共享这些二进制文件。我的机器无需重新构建同事已经构建过的模块。这就是大型团队能将全量构建时间从分钟降到秒级的原因。


什么时候适合引入,什么时候会过度?

Tuist 很强大,但引入它也意味着把一个工具加入团队的必需依赖。

情况 判断
少于 10 个模块,小型团队 使用 SPM 本地包就足够
几十个模块,pbxproj 每周都发生冲突 引入价值很高
CI 和团队构建时间开始造成成本问题 仅缓存功能就足以成为引入理由
团队完全没有精力负责构建系统 需要考虑学习曲线和跟进更新的成本

如果决定引入,建议先用 Tuist 管理新模块,再逐步迁移现有 target,而不是一次性全面切换。XcodeGen 是更轻量的替代方案,只负责生成项目;如果不需要缓存,也可以一并评估。

面试中可以这样回答

问:为什么要引入 Tuist 这样的项目生成工具?

因为从仓库中移除 pbxproj 可以消除合并冲突,而用 Swift 代码声明项目配置可以标准化模块模板。此外,二进制缓存还能跳过未变化模块的重新构建,降低模块化的管理成本,同时放大构建速度收益。

问:随着模块数量增加,如何从团队层面缩短构建时间?

引入模块级二进制缓存,用预构建产物替代未变化的模块,再通过远程缓存让团队和 CI 共享。开发者只以源码形式打开自己正在开发的模块,因此全量构建成本会与工作范围成正比。

把依赖图显示在屏幕上,讨论会更高效
把依赖图显示在屏幕上,讨论会更高效

至此,模块化系列结束。我们从边界和内聚性出发,依次讲到依赖方向、分层结构、SPM、构建速度,最后来到 Tuist。

按顺序实践这些方法,你获得的不只是“构建更快”,还会拥有一个“不再害怕修改”的代码库。