ソフトウェア設計

[モジュール化 #6] TuistでiOSのモジュール化が楽になる理由(pbxprojの競合・バイナリーキャッシュ)

このシリーズに沿ってモジュールを数十個まで増やしたチームは、やがて新しい種類の疲労に直面します。

読了 5 分
[モジュール化 #6] TuistでiOSのモジュール化が楽になる理由(pbxprojの競合・バイナリーキャッシュ)のカバー画像

このシリーズに沿ってモジュールを数十個まで増やしたチームは、やがて新しい種類の疲労に直面します。

モジュールを追加するたびに繰り返す設定作業、残り続けるプロジェクトファイルの競合、そしてメンバーごとに少しずつ異なるモジュール構成です。

Tuistはまさにこの問題を狙うツールです。一言でいえば、Xcodeプロジェクトファイルをリポジトリからなくし、Swiftコードによる宣言から生成します。

この記事では、Tuistが解決する問題、Project.swiftの基本、そして大規模チームで真価を発揮するバイナリーキャッシュまで整理します。2026年7月時点のTuist 4が基準です。


Tuistはどのような問題を解決しますか?

ポイントは、pbxprojの所有権を人からツールへ移すことです。

一般的なiOSプロジェクトでは、project.pbxprojをリポジトリにコミットします。ファイル追加やターゲット設定の変更はすべてこのファイルに影響しますが、形式も人間には読みにくいため、マージ競合の解消は厄介です。

Tuistプロジェクトでは、このファイルをコミットしません。代わりにProject.swiftの宣言をコミットします。

比較 一般的なプロジェクト Tuistプロジェクト
リポジトリにあるもの project.pbxproj Project.swift(Swiftコード)
プロジェクトファイル 手動で管理 tuist generateで毎回生成
マージ競合 pbxprojで頻発 Swiftコードの差分なのでまれ
モジュール追加 GUI操作+設定の繰り返し 関数呼び出し1行

プロジェクトファイルが生成物になった瞬間、マージ競合の対象から外れます。.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に似ています。違いは拡張性にあります。

チーム標準のモジュール構成をヘルパー関数として定義しておけば、新しい機能モジュールの追加が本当に1行になります。

// チーム標準:機能モジュール=ソース + テスト + デモアプリ一式
let searchModule = Target.featureModule(name: "Search")
let orderModule = Target.featureModule(name: "Order")
// モジュールあたりのターゲット数。設定はヘルパー内で統一 3

モジュールが30個ある場合、pbxproj方式では設定画面を30回繰り返しますが、Tuistでは配列に名前を追加するだけです。メンバーごとの設定ずれもなくなります。

依存グラフの可視化も組み込まれています。tuist graphコマンド1つでモジュールの依存関係を図にでき、循環依存や前回警告した「全員が依存する肥大化モジュール」も目で確認できます。

宣言からプロジェクトが生成される流れ
宣言からプロジェクトが生成される流れ

バイナリーキャッシュ、ビルド速度の次の段階

前回はモジュール化によって再コンパイル範囲を減らせると説明しましたが、Tuistのキャッシュはさらに一歩進みます。

tuist cacheは各モジュールを事前ビルドし、バイナリー(フレームワーク)として保存します。その後tuist generateを実行すると、変更されていないモジュールはソースではなくキャッシュ済みバイナリーに置き換わります。

  • 検索機能だけを開発しているなら、検索モジュールだけをソースで開き、残り数十個のモジュールはビルド済みバイナリーで受け取ります
  • クリーンビルドでさえ、実質的に「自分のモジュール+リンク」だけになります

リモートキャッシュを使えば、これらのバイナリーをチームとCIで共有できます。同僚がすでにビルドしたモジュールを自分のマシンで再ビルドする必要はありません。大規模チームでクリーンビルドが分単位から秒単位になった事例が出る理由です。


いつ導入し、いつ過剰になりますか?

Tuistは強力ですが、チーム必須の依存関係として1つのツールを追加する決断でもあります。

状況 判断
モジュール10個未満、小規模チーム SPMのローカルパッケージで十分
モジュール数十個、pbxprojの競合が毎週発生 導入する価値が大きい
CI・チームのビルド時間がコスト問題になる キャッシュだけでも導入の根拠として十分
チームにビルドシステム担当の余力がまったくない 学習コストとアップデート追従コストを考慮

導入するなら全面移行より、新規モジュールからTuistで管理し、既存ターゲットを段階的に移行する方が安全です。プロジェクト生成だけを担う、より軽量な代替案XcodeGenもあるので、キャッシュが不要なら併せて検討してください。

面接ではこう聞かれます

Q. Tuistのようなプロジェクト生成ツールを導入する理由は何ですか?

pbxprojをリポジトリから除いてマージ競合をなくし、プロジェクト構成をSwiftコードで宣言してモジュールテンプレートを標準化できるからです。さらにバイナリーキャッシュで変更のないモジュールの再ビルドを省き、モジュール化の管理コストを下げながらビルド速度の利点を高められます。

Q. モジュールが増えたとき、チーム全体でビルド時間を短縮する方法を説明してください。

モジュール単位のバイナリーキャッシュを導入し、変更のないモジュールをビルド済み成果物に置き換え、リモートキャッシュでチームとCIに共有します。開発者は作業中のモジュールだけをソースで開くため、クリーンビルドのコストが作業範囲に比例します。

依存グラフを画面に表示しておくと会話が速くなります
依存グラフを画面に表示しておくと会話が速くなります

これでモジュール化シリーズを終えます。境界と凝集度から始まり、依存方向、レイヤー構造、SPM、ビルド速度、そしてTuistまで扱ってきました。

順番に適用していけば、「ビルドが速くなった」だけでなく、「変更するのが怖くないコードベース」という、より大きな成果を得られるはずです。