ハイライト
Xcode 16 はモジュールビルドをコンパイラの暗黙動作から scan、build modules、compile sources の 3 つの明示フェーズに分割し、ビルドシステムがモジュール依存グラフを完全に把握してタスクを効率的にスケジュールし、実行レーンのブロックを回避し、デバッガがコンパイル済み Swift モジュールを再利用できるようにします。
主要内容
同じプロジェクトで clean build と増分ビルドの時間差が大きく、増分ビルド時間がほぼ予測不能 — こういう経験はありませんか?原因はモジュールビルドが常にコンパイラの暗黙動作だったことです。各コンパイルタスクが import に遭遇すると自らモジュールを探し、コンパイル要否を判断し、他タスクがコンパイル中なら待機 — この調整プロセス全体がビルドシステムから見えません。結果として実行レーンがブロックされ、コンパイルタスクの所要時間がばらつき、最初のバッチが特に長くなります(モジュールコンパイル完了を待つため)。さらにデバッガ(LLDB)は独自のモジュールグラフを持ち、p や po で式を評価する際にモジュールを再コンパイルする必要があります(08:03)。
Xcode 16 で導入された explicitly built modules はこの暗黙フローを 3 つの明示フェーズに分割します:scan(ソースファイルをスキャンしてモジュール依存グラフを構築)、build modules(モジュールをコンパイル)、compile sources(ソースファイルをコンパイル)(06:01)。ビルドシステムはモジュール間の依存関係を完全に把握し、モジュール準備完了後にコンパイルタスクをスケジュールでき、無効なタスクで実行レーンが埋まるのを防ぎます。C と Objective-C コードではデフォルト有効、Swift では Build Settings の “Explicitly Built Modules” で有効化(preview 段階)(08:42)。デバッガもビルドシステムがコンパイル済みの Swift モジュールを直接再利用でき、別途再構築不要です(08:03)。
詳細
モジュールの本質
モジュールはライブラリやフレームワークのインターフェースを記述するコード配布単位です。Swift では 1 ターゲットの全ソースファイルが 1 モジュールを構成し、インターフェースは public などのアクセス修飾子でマークされ、コンパイラが .swiftinterface と .swiftmodule を生成します。Objective-C ではヘッダーファイルと module map でモジュールを記述し、コンパイル後 .pcm(precompiled module)を生成します(00:34)。
module map の構造例:
framework module UIKit {
umbrella header "UIKit.h"
export *
module * { export * }
}
キーポイント:
framework module UIKitは UIKit という名前のフレームワークモジュールを宣言、@import UIKitはこの名前で動作umbrella header "UIKit.h"は全ヘッダーを含むアンブレラヘッダーを指定export *とmodule * { export * }はこのモジュールがインポートしたモジュールもインポート側で利用可能(01:46)
暗黙 vs 明示モジュールビルド
暗黙ビルドではコンパイラ同士がモジュールコンパイルを自己調整 — 先着がコンパイル、後着は待機。複数コンパイルタスクが同時に同じモジュールをコンパイルしようとし、1 つだけ成功、残りはファイル存在を確認して直接ロード。ビルド時間が予測不能になり、ビルドログでもモジュールコンパイル時間が見えません(04:25)。
明示ビルドでは Xcode が各ソースファイルのコンパイルを 3 フェーズに分割:
- Scan:各ソースファイルをスキャンしてプロジェクト全体のモジュール依存グラフを構築、ビルドログでは scan タスクとして表示。組み込みタスクで新プロセス不要、ソースファイル間で情報をキャッシュ可能(09:27)
- Build modules:モジュールグラフに基づきモジュールコンパイルタスクをスケジュール、各モジュールは独立コンパイラプロセス。トップレベルタスクで特定ターゲットに所属せず、モジュールはターゲット間で共有可能(09:47)
- Compile sources:ソースファイルをコンパイル、必要なコンパイル済みモジュールが既知で注入済み、コンパイル時間が大幅短縮(06:36)
モジュールバリアント(Module Variants)
同じモジュールが複数回コンパイルされることがあります — ターゲットごとに build settings が異なり、異なるモジュールバリアントが必要なため。各バリアントにはハッシュ値があり、そのバリアント構築に必要なコマンドライン引数集合を表します(10:26)。一般的なバリアント要因:
- 追加プリプロセッサマクロ(例:
ENABLE_FEATURE) - 異なる言語モード(例:Objective-C プロジェクトに混在する C ファイル)
- 異なる言語バージョン(例:C17 併用の Objective-C)
- Automatic Reference Counting(ARC)無効(12:09)
Product > Perform Action > Build with Timing Summary でビルドレポートを生成し、ビルドログで “modules report” を検索して Clang と Swift のモジュールバリアント数を確認できます(12:46)。
モジュールバリアントの削減
セッションでは ResearchKit プロジェクトの最適化をデモ。あるターゲットに追加の ENABLE_FEATURE マクロがあり、UIKit などのモジュールに追加バリアントが必要でした。このマクロをターゲットレベルからプロジェクトレベルに移すと、モジュールバリアント数が 4 から 3 に減少(13:14)。
原則:build settings は可能な限り高いレベル(project または workspace)に設定し、ターゲットレベルではなく。これでモジュールをソースファイル間で最大限共有できます(14:28)。
重要ポイント
-
何をするか:C/Objective-C/Swift 混在プロジェクトで Explicitly Built Modules を有効化。なぜ価値があるか:Xcode 16 では C/ObjC のデフォルト動作、ビルドがより信頼性高く効率的、デバッガがモジュールを再コンパイルしない。始め方:Build Settings で “Explicitly Built Modules” を検索、Swift は現在 preview、小さなプロジェクトで試す。
-
何をするか:Build with Timing Summary と modules report でモジュールバリアントを診断。なぜ価値があるか:余分なバリアントは重複コンパイル、時間と CPU を浪費。明示モジュールビルドがこの問題を表面化させ、解決可能に。始め方:Clean Build Folder 後に Product > Perform Action > Build with Timing Summary を実行、ビルドログで “modules report” を検索してバリアント数を確認。
-
何をするか:プロジェクト全体で build settings を統一してモジュールバリアントを削減。なぜ価値があるか:ターゲットごとに異なるマクロや言語バージョンで同じモジュールが複数回コンパイルされ、ビルドが遅くなる。始め方:各ターゲットのプリプロセッサマクロ、言語バージョン、ARC 設定を確認、統一可能なものを project レベルに移動。Build Settings の Levels ビューでターゲットとプロジェクトの差分を比較。
関連セッション
- What’s new in Xcode 16 — Xcode 16 の全体改善概覧、ビルド・デバッグ・診断の更新を含む
- What’s new in Swift — Swift 言語更新、Swift Build の新機能紹介
- Migrate your app to Swift 6 — Swift 6 移行実践、モジュール単位の段階的移行戦略
- Xcode essentials — Xcode 開発ワークフローの核心テクニック、ビルド最適化と併用
コメント
GitHub Issues · utterances