Xcodeのビルド待ちで消耗してたので見直したら50%以上削減できた話

# まえがき
「ビルド待ちすぎて頭おかしくなりそう:joy:」 
そうやって消耗されてませんか？自分もそうでした。

でも巷にはXcodeでのビルド時間を削減・短縮する様々な方法がシェアされております。
そこで、実際に自分が開発しているProjectに対してそれらの方法を**ちゃんと適用**して、どの程度効果があったかを実測値とともに記しておければと思い執筆しました。

* **ビルド時間が長くて作業に集中できないよ**っていう方
* **よく何％削減とかいうけれど本当に実測値でみても効果あるの？**と疑問を抱いている方

の参考になれば幸いです。

本記事でも、改善方法についての解説は行なっておりますが、先人様方の記事の方がより詳細に記されているため、そちらも合わせてご参考にしていただければと思います。同時に、今回の効果測定でも下記記事は大変参考になりました。ありがとうございます:bow:

* @you_matz さん： [Xcode8でビルドが遅すぎるのを解消した件（Swift パフォーマンス改善）](http://qiita.com/you_matz/items/e95f30023eccc8d96357)
* @manshiro さん： [Xcode8でのSwiftのビルドが遅い](http://qiita.com/manshiro/items/be6f6c5406982ecb7d03)
* Stack Overflow： [ios - XCode 8.0 Swift 3.0 slow indexing and building](https://stackoverflow.com/questions/39547197/xcode-8-0-swift-3-0-slow-indexing-and-building/39949491)

# 検証環境
今回のビルド時間削減を行う前の状態のスペックを晒しておきます。
詳細な設定値などは、個々のビルド短縮方法の紹介とともに記します。

* Swift3.1と一部Objective-Cからなるそれなりに巨大なプロジェクトファイル(数百〜数千ファイル規模)
  * 過度にnil合体演算子を使わない、lazy varは基本的に使わない方針
  * 割と厳密にファイルアクセス修飾子(finalやprivate)を設定している
  * Cocoapods & Carthage使用
      * 今の時点でpodからCarthageに移行できるframeworkは無い
  * 大人数で開発しているので1秒遅いだけでも積み重なってかなり変わってくる
* ビルドにはXcode 8.3.3/iOS10.3.1 Simulatorを使用
  * Build Only DeviceだとSimulatorに比べてビルド時間が少し多くかかるため
* PCはMacBook Pro 15inch （2013）を使用
* 検証は、Xcodeを再起動し、Derived Dataファイルを削除し、念のためClean(*Cmd + Shift + Opt + K*)してからBuildした際の時間を比較して実施
* 純粋なビルド時間を計測するため、他のジョブは基本的に閉じた状態で実施
  * そのため、通常時は別のジョブも動いていることを考えるともう少し時間は伸びそうに思えます

# Before & After
先に簡単な結果だけ載せておきます。（一番最後に詳細な結果を載せています）
*Debug*ビルドではビルド時間を半分以上早くすることができました:tada:
が、*Release*(*InHouse*)ビルドではあまり効果が見られなかったと言う結果になりました。

||Debugビルド|Release(InHouse)ビルド|
|:--|:--|:--|
|Before|266s|384s|
|**After**|**110s**(58.6% :arrow_heading_down:)|373s(1.8% :arrow_heading_down:)|


# やったこと
それでは本題に入っていきます。下記の施策を行いました。

【番外編】 不要なBuildをせずにRunする
【下準備】 ビルド時間を表示できるようにする
1. Optimization Levelの見直し（Debugビルドのみ適用）
2. Build OptionsのDebug Information Formatの見直し
3. コンパイルに時間のかかるメソッドを検出してリファクタリングする
4. コンパイル時のタスク数を増やしてマルチタスクにする（結果的に変更なし）

## 【番外編】 不要なBuildをせずにRunする
特に前回ビルドから変更もしてないのにRun(Debug)するたびに再度Buildさせてしまってたりしてませんか？
まずビルド削減施策を行う前に、自身のやり方を一度振り返って見ることをお勧めします！

Runは```Cmd + R```で行なっている方がほとんどかもしれませんが、**ビルドせずに前回のビルドの状態でRunする**方法があります。それは以下のショートカットで実現できます。
```Cmd + Ctrl + R```
このシチュエーションの場合は、毎回かかっているビルド時間を**ゼロに削減**できます。
まだビルドされてない場合もダイアログが出てくれるので```Build & Run```をタップすれば通常のRunが実行されます。
知らない方、あまり使用されていない方は今一度覚えておくとよいかもしれません。
<img src="https://qiita-image-store.s3.amazonaws.com/0/128422/3d73ca63-dc3d-5879-af44-4f740c03c2a0.png" width=500>


## 【下準備】 ビルド時間を表示できるようにする
先ほどの結果はどうやって測ったの？と思う方もいらっしゃるとは思いますが、実はコマンド一発でXcodeのGUI上に表示することができます。デフォルトにしてほしいくらいです。
まずは己の現状を知るということで、まだ設定されていない方がいらっしゃいましたら、ぜひこれを機に設定して見てください。

ターミナルにて下記コマンドを実行しましょう。
```defaults write com.apple.dt.Xcode ShowBuildOperationDuration YES```

ビルドが成功するとSucceededの右側に表示されるようになります。
![image](https://qiita-image-store.s3.amazonaws.com/0/128422/61f2241b-d626-0069-7b1a-92c9983352fc.png)

## 1. Optimization Levelの見直し
### Build Settings > Swift Compiler > Optimization Level
*Debug*ビルドの場合は頻繁にビルドを行うため、最適化をかけるとかえってビルド時間を増やしてしまうようです。
そのため、Noneを設定しておきましょう。おそらくデフォルトでもその設定になっているため、もし変えてしまっている方がいらしたら一度見直してみてください。
（もちろん、Releaseビルドの際は最適化が効くようにFastなどを設定しておきましょう。）
<img src="https://qiita-image-store.s3.amazonaws.com/0/128422/8a8b27f6-073d-dc34-a654-1d4e33fa8c6e.png" width=500>

### SWIFT_WHOLE_MODULE_OPTIMIZATIONフラグを有効化
**この設定がDebugビルドのビルド時間を劇的に改善してくれました**:tada:
設定の仕方は、Build Settingsを開き、その下の+ボタンからAdd User-Defined Settingをタップします。
<img src="https://qiita-image-store.s3.amazonaws.com/0/128422/bf5edb25-7b3e-32e2-fac1-62b80c01a1f6.png" width=300>

そして、下記のようにKey/Valueを設定するだけです。
もちろん、ConfigurationごとにYES/NOを設定することも可能です。
```SWIFT_WHOLE_MODULE_OPTIMIZATION YES```
<img src="https://qiita-image-store.s3.amazonaws.com/0/128422/9b123a98-2344-7092-4d0a-5b76fc031c10.png" width=500>

*Debug*ビルドでは、Optimization LevelはNoneに指定しているにもかかわらず、こちらのOptimization設定をYESにすると**ビルド時間短縮を実現できました**。（なぜこれが効いているのかは正直あまりよくわかっておりません。）
*InHouse*（*Release*）ビルドでは、もともとOptimization Levelがwhole module optimizationになっているからか、**全く効果がありませんでした**:joy:

ただ、ビルドログを眺めているとわかったのですが、フラグ未設定時は一ファイルごとにコンパイルログが吐かれていたログが、下記のようなログしか出なくなりました。全体モジュール最適化が効いているようです。
<img src="https://qiita-image-store.s3.amazonaws.com/0/128422/3c6d17bc-df28-faf0-7542-e19f81d9628f.png" width=300>

また、この設定を行うとフルビルドの時間は改善されたとしても、差分ビルドの時間がフルビルドと同じくらいかかるようになるといった書き込みを見ましたが、当方の環境では差分ビルドの時間は設定前と体感的にほとんど変わらず、フルビルドよりも短い時間で行えています。むしろ今までは差分ビルドの際も数分かかっていた場合があったので、もろもろ鑑みると大いに効果がありそうです。
他にも、debug時のpoコマンドを使うとクラッシュするなどの記事もありましたが、自分の環境では問題なく使えています。

## 2. Build OptionsのDebug Information Formatの見直し
こちらも、Optimization Level同様で最近のXcodeでProjectを作成するとデフォルトで設定されているため、効果がない方もいらっしゃるとは思いますが、古くからのProjectファイルを編集している方は一度見直してみると良いかもしれません。

```Build Settings > Build Options > Debug Information Format```
には```DWARF with dSYM File```および```DWARF```が設定できます。

dSYMファイルを必要としないConfigurationの際は、DWARFにしてみましょう。
dSYMはCrashファイルの解析に必要となるため、クラッシュ検知などに利用されている場合は設定変更できないのでご注意ください。当方では、Debugビルドの時のみdSYMが不要なため、DWARFへと変更しました。

## 3. コンパイルに時間のかかるメソッドを検出してリファクタリングする
[Build Time Analyzer for Xcode](https://github.com/RobertGummesson/BuildTimeAnalyzer-for-Xcode)
というファイルごとのビルド時間を計測してくれるツールがあります。

非常に便利なツールなので一度は使っていただきたいですが、そのツールを毎回のデバッグで使うにはちょっと荷が重かったりするため、もっとお手軽に利用できるのがこちらのフラグを設定する方法です。
Build Settings > Other Swift Flagsに以下を定義します。
```-Xfrontend -warn-long-function-bodies=100```
<img src="https://qiita-image-store.s3.amazonaws.com/0/128422/999f856f-47d6-63d0-0857-b567667e76a4.png" width=500>

こちらでは、メソッド単位でビルド時間が長くかかっているものに対してwarningを表示できます。
```/Users/ruwatana/Hoge/ViewController.swift:100:10: Instance method 'hoge()' took 1000ms to type-check (limit: 100ms)```

ひとまず100ms以上かかるメソッドを警告表示させ、下記ページを参考に警告が出なくなるまでリファクタリングを行いました。主にCGFloatの型判定あたりが無駄に時間がかかっていました。
[Regarding Swift build time optimizations](https://medium.com/@RobertGummesson/regarding-swift-build-time-optimizations-fc92cdd91e31)

ただし、これに関しては当プロジェクトがある程度コーディング規約として対策がなされていたということもあり、体感するほどの改善はほとんど見込めませんでした。（1メソッドで最大数秒の改善程度）
ただ、こちらも知らなかった方は、頭のどこかに入れておくとよいかもしれません。

## 4. コンパイル時のタスク数を増やしてマルチタスクにする
結論から述べますが、こちらは**効果がありませんでした**:joy:

他の記事などでは、Xcodeはデフォルトでは1つのタスク上でしかビルドは実行されないのでタスク実行数を複数に変えるとビルド時間が早くなると書いてあったりしますが、未設定の状態で、実際のビルドの動きをみているとMacBook Pro 15inchでは4〜8個くらいのタスクが動的に動いていました。
つまり、**元から最適化されている**といえそうです:v:

ちなみに、タスク実行数の変更はターミナルにて下記コマンドを入力すると行えます。
ためしに、当方の4コアのCPUを搭載したPCで4に設定して試してみましたが、やはり元の方が早く、少し遅くなったという結果になりました。

```bash:Terminal
defaults write com.apple.dt.Xcode IDEBuildOperationMaxNumberOfConcurrentCompileTasks 4
```


間違えて設定されてしまっている場合は、下記コマンドで消すこともできるので、一度設定を見直して見るのが良いと思います！

```bash:Terminal
defaults delete com.apple.dt.Xcode IDEBuildOperationMaxNumberOfConcurrentCompileTasks
```

# 結果
最後まで、お読みいただきありがとうございます:bow:
今回の改善施策では、Debug環境でのビルド時間を半分以上削減することができました:tada:
ただし、Release環境のビルド時間は大して変わらないと言う結果となりました。
とはいえ、普段デバッグ作業に使うのはDebugビルドが多いのでこれだけでもかなり作業効率の改善が見込めそうです。
ほとんどがコピペやポチポチで済むものであり、かなり手軽に改善が見込めるので、試す価値は大いにあるかなと思います！
ぜひ、この他にもビルド時間の改善が見込める施策を知っている方がいらっしゃいましたら、教えていただきたいです。

最後に、ここで紹介した施策別の効果測定結果を載せておきます。
最終的に2分を切ったのはとっても感慨深いです！

|改善施策|Debugビルド|Release(InHouse)ビルド|
|:--|:--|:--|
|Before|266s|384s|
|1. Optimization設定の見直し|122s(54.1% :arrow_heading_down:)|-|
|2. Debug Information FormatをDWARFに変更|247s(7.1% :arrow_heading_down:)|-|
|3. コードリファクタ|264s(0.7% :arrow_heading_down:)|373s(1.8% :arrow_heading_down:)|
|~~4. マルチタスク実行(4コア)~~|遅くなった|遅くなった|
|**合わせ技(After)**|**110s(58.6% :arrow_heading_down:)**|373s(1.8% :arrow_heading_down:)|