ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
Xcodeでビルドスピードを上げる
Xcode 10でAppのビルドスピードを上げましょう。すべてのプロセッサコアを最大限に活用するために、プロジェクトを構築する方法やコードを調整する方法について紹介します。試しにコードを少しだけ調整してみる場合でも、リリースに向けて完全なAppをビルドしている場合でも、ご紹介するテクニックを活用すれば、Appのビルドにかかる時間を節約することができます。
リソース
-
ダウンロード
(音楽)
(拍手) こんにちは ご来場ありがとう Xcodeチームのデビッドです 今日は Swiftチームの ジョーダンとともに Xcodeのビルド高速化の 話をします プロジェクトやその構成によって 様々な改善策が使えます 場合によっては ビルドを 著しく高速化できます
今日は ビルド高速化について 2つの視点で話します
1つは全体的な ビルド効率の向上です もう1つは リビルド時の 作業量の軽減 特に インクリメンタルビルドの 話です
これから私が説明するのは プロセスの並列化や Run Scriptフェーズの扱いです Xcode 10のビルド時間測定の 新機能も紹介します
また ジョーダンが ソースレベルの話をします 例えばSwiftでの依存関係の理解 複雑な式の扱い方 Objective-Cの インターフェース制限などです
まずは ビルドの並列化です
Xcodeのプロジェクトでは ビルドしたい対象を ターゲットで指定します 例えば アプリケーション フレームワーク ユニットテスト
また ターゲット間には 依存関係があります 依存関係を定義する方法は2つ Target Dependenciesによる 明示的な方法と Link Binary With Libraries などの暗示的な方法です 後ほど もう少し詳しく 説明します サンプルを見ながら 話をします プロジェクトの依存関係の図です 単純に 全ターゲットが 並んでいます ここでは 5つをビルドします 依存関係も示しています
この2つの情報により Xcodeはビルドを行います
タイムラインを見てください
各ターゲットが 順番に処理されます 1つが終わってから 次へ進みます 何の問題もないのですが ハードウェア利用率に 無駄が生じます マルチコアのマシンなどは 特にそうです 貴重な開発時間が奪われますね そこで 次を見てください
これを見て気付くことは まず ビルドの処理量は 変わっていません しかし 時間は短縮されました この例では 大幅に減っています
ハードウェアを 最大限に活用して 時間を削減できました
では 並列化というものが これほど有効なら この図のように 全部 同時にしてみては? まず エラーが出るのが 関の山でしょう 依存関係の情報が 重要な要素となるからです このようにすると 依存関係を無視して ビルドすることになります うまくいきません では どうしましょう ビルドの並列化で 時間を短縮するには どうすればよいのでしょうか Xcodeは本来― ビルド並列化ができるように 設定されています スキームエディタを使います まず Scheme Chooserを開き Edit Schemeを選択 Build Actionの中に Build Optionsがあります 項目は2つです “Parallelize Build”と “Find Implicit Dependencies” です 前者を選択します Xcodeが依存関係の情報を 利用して ターゲットの並列化を 試みます
では 依存関係の構成を 見てみましょう ビルドフェーズのエディタで 見られます プロジェクトナビゲータを 開いて プロジェクトを選択 ここでは ゲームプロジェクトです
次にBuild Phasesをクリック
ゲームターゲットのところを 見てください 依存関係を確認できます Link Binary With Librariesに 注目してください このフェーズでは ターゲットとリンクさせる アイテムを定義します ここでは Physicsと Utilitiesがあります
これがプロジェクト内の ターゲットで Xcodeが依存関係を 作成しています Autolinkなどのリンク機能や LD Build Flagsなどを 用いている場合 暗示的な依存関係の 作成はできません 明示的な依存関係を このフェーズか― Target Dependenciesフェーズで 作成します さて このシェーダという アイテムは
リンク時には使用しません 別のビルドフェーズで 使用されます それをXcodeに 知らせることが重要です ターゲットをビルドする前に シェーダのビルドとコンパイルも 完了させます
このターゲットが含まれる プロジェクトを参照するには これを 現在のプロジェクトの 下位にドラッグします
その他の依存関係を見ると
シェーダはUtilitiesに依存 UtilitiesはPhysicsに 依存しています 最後に Testsは ゲーム シェーダ Utilitiesに 依存しています プロジェクトの 構成を理解しました 次に この連続的な ビルドプロセスを 並列化する手順を説明します まず テストに注目します
これから話す依存関係を 3つに分類しました 1つ目は“全部やる”依存と 名付けました このテストは 対象の コンポーネントが多すぎます ゲーム シェーダ Utilitiesの 3つです
こういう場合は 個別に 分割する方がいいでしょう
これによって 並列化の 最初の段階に入ります
テストターゲットが 3つに分かれ ゲームのテストだけを ビルドできます
他の2つは別のターゲットと 並列化できます 各コンポーネントが終わり次第 ビルド可能です
次に説明する依存関係は “うるさい隣人”というものです この依存は なくてはならないものです しかし 必要なのは ターゲットの一部なのに 全体を取得しています ゲームは3つのターゲットに 依存しています これはいいでしょう
怪しいのはシェーダと Utilitiesの関係です シェーダはメタライブラリを 生成します これはGPUコードを まとめたものです Utilitiesが生成するフレームは CPUコードです ですから この依存関係は 疑問です Utilitiesの中に ビルドフェーズがあります これは両ターゲットが使う 情報を生成します しかし Utilitiesの その他の部分は不要です ですから 別ターゲットに 分割しましょう この少しの変更が 全体のタイムラインに 大きな影響を及ぼします
緑色が新たなターゲットです UtilitiesはCode Genに移動し 縮小できました Code Genは 他に依存しないため 最初の方に移動できます 赤で示したPhysicsとも 並列化できます
シェーダはもう Utilitiesに 依存していないため 他のビルドを待つ必要は ありません Code Genの後に ビルド可能です
最後は“忘れられた”依存です
コードを移動したり 削除したりする過程で いわゆるデッドコードが 生じます 依存関係でも同じです 時々 削除を忘れます その場合は 単純に 削除するだけです
ビルドは さらに短縮されます Utilitiesターゲットのビルドを Code Genの 直後にできるからです
これまで Xcodeでは 他に依存するターゲットを ビルドする時― 依存先のビルドを 待つ必要がありました Xcode 10の新機能で ビルドの一部を 並列化することが可能です
コンパイルの開始が 早まります 依存関係を含む ビルドが完了すれば すぐ始められるからです リンクなども並列化できます
Run Scriptは ビルドフェーズの1つです このフェーズで 並列化を利用するには ターゲットが順に 完了するまで待たされます 実は このフェーズでは プロセスを 自由に カスタマイズできます 柔軟な分 デベロッパの責任も 大きくなります それでは Run Scriptを設定し ビルドを効率よく行う プロセスを説明します
これが Run Scriptフェーズの エディタです ビルドフェーズのエディタにも あります まず スクリプト本体を 見てください ここに スクリプト全体を入れるか 別の場所を参照します
フェーズ全体を通して 利用できる設定があります その1つ Source Groupを 使っています
これを使うと 絶対パスや相対パスを 提供する必要がなく 便利です
次は入力ファイルです このフェーズでは重要です この情報を利用して ビルドシステムはRun Scriptの 実行を決定します そのため 処理中に読まれるファイルを 漏れなく含める必要があります
入力するファイルが 多いこともあります 大変そうに思えますね Xcode 10では― このリストを外部ファイルで 管理できます File Listといいます
単純なテキストで ファイルを リスト化したものです
Run Scriptフェーズを通して 同じビルド設定に アクセスできます
ただし ビルドの過程での ファイルの修正や生成は不可です ビルドが始まると リストが読まれ 情報が使われます
次は出力ファイルの話です これも ビルドに必要な 重要情報の1つです Xcodeはこの情報から Run Scriptの実行を決めます
もちろん 出力ファイルにも 新機能は対応しています Run Scriptの実行は どんな時でしょうか 入力ファイルの宣言がないと ビルドのたびに 実行する必要があります ですから 入力の宣言は重要です
次に 入力ファイルや File Listの内容に 変更があった場合です Run Scriptは戻されます
出力ファイルが 見つからない場合― XcodeはRun Scriptを 実行します 足りないファイルを 生成するためです
Xcode 10では Run Scriptフェーズの ヘルプがあります (拍手) 今 話した内容を含めて さらに詳しい情報が 書かれています File Listを使う方法もです
さて Run Scriptを設定して 新たな依存関係を 宣言したとします 依存サイクルに 陥ることがあります 依存関係の どこかが ループしている状態です
Xcode 10では このサイクルの 検出力が向上しました サイクルを形成している 入力ファイルを 漏れなく把握できます
サイクルが良くないのは まず プロジェクト内の 構成に問題が出るからです 次に 間違ったリビルドや 古い情報の原因になります
そのため ヘルプを更新しました 特に陥りやすい依存サイクルや
その修復方法について 取り上げています
最後の話題は ビルド時間測定です これについては 新機能が2つあります
まず 1つ目 各タスクの実行時間が分かる 行内の時間表示です
ビルドログを見てください 上にフィルターがあります “All”と“Recent”に注目
“All”では 最終物を生成する― すべてのタスクが表示されます 通常は 必要ありません 何か問題がないかを 見たい時は “Recent”タブを使います 前回のビルドで使用した― パスをすべて表示します
もう1つの新機能は Timing Summaryです Productメニューから行けます Perform Actionを選び Build with Timing Summaryへ すると ビルドログの最後に ログが追加されます
詳しく見ると 前回のビルドの 全タスクの合計時間が 分かります やはり Recentタブは重要です
特に注目すべきは PhaseScriptExecutionです 前回のビルドを見ると シェルスクリプトを 実行しています この1つで5秒です インクリメンタルビルドの 1つ1つが― こうなっていたら 設定に不備があります 対処すれば ビルド時間を減らせます
このTiming Summaryは コマンドラインからも 利用することができます
では ここからはジョーダンが ソースレベルの話をします (拍手)
ありがとう やり方を1つ変えるだけで プロジェクトを改善する方法を 紹介しました ソースやファイルの話の前に Xcode 10の新機能を もう1つ紹介します この技を使っている人も いると思います ファイルが多い時の対策です
ご存じでしょう Whole Moduleモードを 使用することです 前バージョンのXcodeでは デバッグビルドでも Whole Moduleの方が デフォルトのIncrementalより 高速でした この方法で ビルド時間を 短縮できました コンパイラがファイル間で 処理を共有できるためです しかし インクリメンタル ビルドを使えず 毎回 全体を リビルドすることになります Xcode 10では インクリメンタルビルドを改良 ファイル間で処理を 共有できます もう Whole Moduleを 使わなくてよいのです
(拍手) 使っていた方は Build Settingsエディタで Compilation Modeの下の Debugを選び Deleteキーを押す これで Incrementalモードに 戻せます ここでは詳しくは触れません “What's New in Swift”でも 触れましたし 明日の“Behind the Scenes of the Xcode Build Process”で さらに詳しい話をする予定です
今日 話すべきことが まだ3つ残っています 1つ目は 複雑な式への 対処法についてです 今回の両セクションの 重要ポイントを 例証するのに最適だからです ビルドに時間がかかる時 ちょっとした情報を Xcodeに与えると 状況を改善できます 複雑なSwiftの式を例に 見てみましょう
私の最近のプロジェクトです この構造体を 私は あちこちで使っています 構造体自体には 問題はありません プロパティの型推定も 問題ありません しかし 型を推定する式が やや複雑です 少なくとも一目で分かる 単純なものではありません これが答えです 0.0のように単純なものなら このDoubleという推定は 不要です しかし この式は 数字が大きく複雑です reduceやpow関数も 使っています “double型”とは 分からなかったでしょう この情報を与えることで この構造体を用いたファイルの 処理を軽減できます さらに 皆さんの同僚が プロパティの型を 把握しやすくなります
このわずかな情報が ビルド時間を大幅に短縮します 優れたエンジニアリングの お手本です 次はクロージャの例です 非オプショナル型の引数の 合計値を返す― 関数を定義しています 3つの引数がnilなら nilを返します
今 ある機能を 使おうとしています 単式を含むクロージャがある時 コンパイラがその式から 型を判定してくれる機能です 非常に便利ですが 時々 こんなコードになります
これはひどい レビューで却下でしょう 入れ子の三項演算子や nilとの明示的比較 これでうまく動くとは 思えません 別の問題もあります 式が大きくなり 独立した断片が多いため Swiftのコンパイラは 時間内に処理できません コンパイラもお手上げという 究極の例です コードを再考しましょう まず 先ほどの例と 同じことをしてみます 型を追加します クロージャでは inの前に入れます
しかし この場合 最善の策とは言えません 前に戻します
先ほど クロージャの型を 判定できるように 単式を書こうとしました しかし ここでは不要です reduceを 呼び出しているのだから オプショナル型の 整数の配列です 結果の型は戻り値と 一致するはずです だから reduceの コールバックは オプショナル型の整数の 演算です クロージャに単式を入れる 必要はないのです ステートメントを分けて 読みやすくできます
私の以前のコードを 変換したものです しかし もっと高速にもできます 読みやすく 管理しやすく コンパイル時間を 短くもできます
最後にお見せする例は 前の2つほどは 広く応用できません AnyObjectの話です AnyObjectはあらゆるクラス型 インスタンスを記述できます 構造体や列挙型ではなく 1つのクラスです Objective-Cのid型から 持ち越した特長もあります それが この メソッド呼び出し構文です AnyObject型の値の メソッドを呼び出すと Objective-Cランタイムから 見える限り― Swiftはそれを許します しかし 代償があります コンパイラは 何が呼ばれるか 分からないため あらゆる可能性を探す 必要があります プロジェクトや フレームワーク すべてを使うと想定します どれも合致しなければ エラーを示すためです
こちらの意思を もっと適切に もっと十分に 宣言できるはずです プロトコルを定義します 同じファイルでも 別ファイルでも可能です デリゲートのプロパティを 変更して このプロトコルを 使うようにします これで呼び出すメソッドが 分かります
メソッドが 正しく実行されているかを 確認する機会も得られます
再コンパイルの際に コンパイラの作業量を 軽減する技を説明しました では 再コンパイルを しなければ? なぜ 再コンパイルが 必要なのでしょうか? 依存関係モデルを 理解しましょう
Swiftの依存関係モデルは ファイルに基づきます ヘッダファイルがないため 少し厄介です デフォルトで定義されたものが すべて見えます 左のファイルで struct Pointを宣言しています 右のファイルでは 最初の宣言を参照し コンパイラも分かっています 右のファイルのxとyの プロパティも同様です
このため 左のファイルを 変更した場合― 両ファイルの再コンパイルが 必要です これが重要なのは このイニシャライザを 正しく呼び出したいからです
コンパイラは 関数本体の変更を 分かっています この場合 アサーションの変更です
このファイルだけ 再コンパイルが必要で 他のファイルは 変更する必要がありません
しかし コンパイラは保守的です このファイルに型を追加すると 人間には 右のファイルに 影響がないと分かります しかし コンパイラは 両方リビルドします
先ほど使用したゲームを 例にします ゲームとUtilitiesがあり それぞれのファイルを 示しています ゲームのファイルを 変更すると
そのファイルの 再コンパイルが必要です それに依存するファイルも 同様です しかし Utilitiesのファイルは 再コンパイルされません ターゲットは分かれ 依存関係にあります 2組のファイル間には 目に見える関係はありません
同様に Utilitiesの ファイルを変更すると 再コンパイルが必要です それに依存する 他のファイルもです しかし この依存関係は 粗いものです そのため Xcodeはゲーム内の すべてを再コンパイルします 関数本体だけの 変更でない限りです
繰り返すと コンパイラは保守的です 影響がないと 人間には分かっても コンパイラは分かりません コンパイラに分かるのは 関数本体の変更です 他のファイルに影響がなく 再コンパイルは不要だと 知っています
ファイルの依存関係は モジュール内で発生し Swiftの宣言は 互いに 暗黙的に見えています モジュールを超える 依存関係を扱う場合は ターゲット全体の 依存になります
いずれもSwiftに関しては 良い情報ですが Objective-Cのターゲットが 混在する場合は? 最後のセクションは そこが焦点です 混合言語の インターフェース軽減の方法です アプリケーションの パーツを見ましょう 少し複雑な図になります 動画なら 遠慮なく 止めながら見てください
まず Objective-C インターフェースのヘッダです Objective-Cで書かれ Swiftに参照させたい部分です あるいは ヘッダで宣言して 他の部分を参照させます
Bridgingヘッダは Swiftに参照させたい― 情報をすべて集めたものです
Xcodeのビルド設定で 管理します すると コンパイラは Objective-Cインターフェースを Swiftに参照させます
コンパイラは 次に 逆の役割を担う― Generatedヘッダを作成 Swiftのどの部分をObjective-Cに 見せるかを記述します
Objective-Cの 実装ファイルで使用され 一部のヘッダは 最初から使えます
もちろん Swiftコードに 依存しないものもありますが ここでは重視しません
もう一度 左から見てみます Objective-Cのヘッダ Swiftに情報を与える Bridgingヘッダ Swiftの実装ファイル Objective-Cに情報を返す Generatedヘッダ Objective-Cの実装ファイルです
このような図では 矢印はすべて依存関係です ターゲット単位ではなく その中のファイル単位の依存です Generatedヘッダと Bridgingヘッダが焦点です このヘッダの内容を減らせば 変更の余地も少なくなるからです リビルドの必要性も減ります
では 見てみましょう Generatedヘッダでは privateキーワードが有効です この例では ViewControllerがあります その中に IBOutletプロパティと IBActionメソッド デフォルトでは Generatedヘッダで見えます Objective-Cが参照するためです プライベートの宣言は ありません
しかし 他のファイルから 見える必要は ほとんどなく Interface Builderで 使うだけです ですから privateにすると この2つが Generatedヘッダから消えます
次の例は #selectorなどの ランタイム機能に用いる― メソッドを扱う場合です NotificationCenter APIを 使っています 通知が送られると selectorを取得します
ここでは Objective-Cに このメソッドが見えればよく その他のファイルは このメソッドを使いません
privateにします Generatedヘッダを さらに削減できます
この場合 別の選択肢もあります ブロックベースのAPIにすれば コードを整理できます 関数から暗黙的に 状態をキャプチャできるからです コンテキストオブジェクトとする 必要はありません
Generatedヘッダを削減する 最後の技は Swift 4に移行することです すでに聞いているでしょうが Swift 3モードのサポートは Xcode 10が最後です ですから いずれにせよ必要です EditからConvertを選び To Current Swift Syntaxへ
ただし この移行を行う時 Swift 3互換モードを 維持する選択を するかもしれません Swift 3 @objc Inferenceです Swift 3の規則を維持したまま 移行する選択肢です 内部のメソッドとプロパティを 自動的に Objective-Cから 参照可能にします
Swift 3で書いている場合でも この機能に頼らないケースが 多かったでしょう ランタイムにも コンパイル時にも不要です そのため Objective-Cの 依存関係を明示的に指定したら @objc IBOutlet IBActionなど いずれの場合でも この設定を選択して Deleteキーを押します 要求を満たす メソッドやプロパティのみ― 属性を推定するモードに 戻ります または Objective-Cの メソッドを無効にします
Generatedヘッダについて 話をしました しかし Objective-Cのコードも 同様に リビルドをもたらします
Bridgingヘッダは Swiftに参照させる ヘッダを集めたものです MyViewController.hに 着目します ごく普通のView Controllerの 宣言です しかし 別のヘッダも 含まれています いずれかのヘッダを変更したら ターゲット内のSwiftコードの 再コンパイルが必要です
これは よくありません
この例を見てください MyNetworkManager.hを インポートするのは このプロパティを宣言する ためだけです
このプロパティは まったく 使われない可能性もあります その場合 ここでの宣言は不要です そこで Objective-Cの Categoryを用いて インターフェースを 切ります MyViewController+Internalを 新たに定義して 追加プロパティを宣言できる Categoryを使います プロパティ合成機能は そのまま利用できます
インポートとプロパティを 下へ移動します 見てください インポートされたヘッダは ずっと小さくなり 不必要なリビルドの原因が 低減しました もう1つあります 定義したファイルは 他に Objective-Cからのアクセスが 不要な場合もあります この場合 別ファイルは必要ありません Categoryを直接 実装ファイルに入れられます それでまったく問題ありません NetworkManagerの プロパティ合成は有効です
おさらいすると プライベートのブロックAPIで ビルド設定をオフにして Generatedヘッダの内容を 縮小しました 次に Objective-Cのヘッダから コンテンツを取り出して Bridgingヘッダを縮小 内容が減れば 処理も減ります 変更の余地も少なくなり リビルドも少なく済みます どちらも有効です
では まとめます
今日 話した内容は Xcodeから 多くの情報を得るとともに 多くの情報を提供して ビルドを高速化する方法です ビルド効率を 向上させることができ リビルドの際の作業量を 減らせます
駆け足になったので 動画もチェックしてください 今日の正午と明日の午後にも ぜひ お越しください ありがとうございました (拍手)
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。