この記事の要点
- Objective-C と Swift が混在するプロジェクトでは、Swift ファイルをコンパイルするたびに bridging header を毎回パースし直すため、ファイル数が増えるほどビルドが目に見えて遅くなることがあります。
- Swift 3.1 では bridging header を一度だけパースして結果を precompiled header(PCH) としてキャッシュし、ターゲット内のすべての Swift ファイルで使い回す新しいモードが追加されました。これは Objective-C や C++ の prefix header に使われている precompiled header の仕組みを応用したものです。
- 開発・テストに用いたプロジェクトでは、このモードによって デバッグビルドの時間が 30% 短縮 されました。短縮効果は bridging header の大きさに依存し、whole-module optimization ビルドには影響しません。
- 当時このモードは実験的な扱いで、
-enable-bridging-pchフラグによる手動有効化が必要でした。
背景: bridging header の繰り返しパース
Objective-C と Swift を混在させるターゲットでは、Objective-C のコードを Swift から見えるようにするために bridging header を使います。Swift コンパイラは、混在ターゲット内の Swift ファイルをコンパイルするたびに、このプロジェクトの bridging header をパースします。
bridging header が大きく、かつコンパイラが何度も起動される場合 ―― デバッグ構成のビルドがまさにこれにあたります ―― bridging header を繰り返しパースするコストがビルド時間全体のかなりの割合を占めることがあります。実際、Swift ファイルを 1 つ追加するたびに全体のビルド時間が目に見えて増える、というプロジェクトもありました。たとえ追加した Swift ファイル自体はごく小さくてもです。
precompiled bridging header で何が変わるのか
Swift 3.1 のコンパイラには、このコストを減らす新しいモードとして bridging header の precompile(事前コンパイル) が加わりました。
このモードを有効にすると、Swift ファイルごとに bridging header をパースし直す代わりに、bridging header は 一度だけ パースされ、その結果が一時的な「precompiled header(PCH)」ファイルとしてキャッシュされます。以降はターゲット内のすべての Swift ファイルでこのキャッシュが再利用されます。これは、Objective-C や C++ のコードで prefix header を事前コンパイルするのに使われているのと同じ precompiled header 技術を利用したものです。
このモードを開発・テストに用いた Swift プロジェクトでは、デバッグビルドの時間が 30% 短縮 されました。短縮の度合いはプロジェクトの bridging header の大きさに依存します。また、このモードは whole-module optimization ビルドには影響しません。あくまでデバッグ構成で繰り返しビルドする際のコンパイル時間を大きく改善するものです。
使い方と注意点
このモードは Swift 3.1 の一部として、swift.org の nightly snapshot と Xcode 8.3 beta で利用できました。
公開当時このモードは実験的な扱いで、手動で有効化する必要がありました。将来のリリースで、開発者からのフィードバックにより十分に機能し大きな高速化が得られると確認されれば、既定で有効化される見込みとされていました。
手動で試すには、対応するコンパイラをインストールしたうえで、プロジェクトのビルド設定を開き、”Other Swift Flags” に -enable-bridging-pch オプションを追加します。
大きな bridging header を持つプロジェクトでは効果が見込めるため、当時の記事は試用とフィードバックを呼びかけていました。
関連リンク
- Swift 3 における whole-module optimization — この記事で「PCH モードは影響しない」とされているビルドモードの解説です。