Swift のビルドを高速化する (30秒→4秒に)

[Classi Advent Calendar 2016](http://qiita.com/advent-calendar/2016/classi) 17日目です！

Swift ファイル数 80 ほどの開発中盤段階のプロジェクトですが、ビルドに時間がかかり開発効率が落ちていたため 2 日ほどかけて高速化を行いました。

ローカルビルドが30秒から4秒ほどになり、 CircleCI のビルド〜テスト完了までの時間を10分から4分になりましたので、 実施内容を書いていきます。

### 3行でいうと

* Build Target の設定を見直し
* [CocoaPods](https://cocoapods.org/) から [Carthage](https://github.com/Carthage/Carthage) に移行しソース管理に含める
* ソースコードを改善

## マシンスペック

一世代前の MacBook Pro で、 CPU は増してないです。

```
MacBook Pro (Retina 13-inch、Early 2015)
プロセッサ: 2.7 GHz Intel Core i5
メモリ: 16 GB 1867 MHz DDR3
```

[ashfurrow/xcode\-hardware\-performance](https://github.com/ashfurrow/xcode-hardware-performance#xcode-hardware-performance) でいうと中の上くらいのスペックです。

## 1. Build Target の設定を見直し

まずおかしいと思っていたのが、Target が複数ある場合全ての Target が Build されていること。

Target は API を本番/テスト環境の切り分けのために3つ用意し、テスト用の Target を1つの計4つ用意していた。

![unspecified.png](https://qiita-image-store.s3.amazonaws.com/0/5678/7530e641-cbe7-6782-1241-e4c71dcc3209.png)


この構成で、 `ClassiDevelop` をビルドしただけなのに `ClassiStaging` と `Classi` もビルドされており3倍時間がかかっていた :cry:

### なぜ指定外の Target がビルドされていた？

`ClassiTests` が `Classi`, `ClassiStaging`, `ClassiDevelopment` の3つに依存していたため、依存されていたもの全てビルドされていた。

それを `ClassiDevelopment` のみ依存するようにした。
こうすると `ClassiDevelopment` でしかテストを実行できなくなるが、それは大きな問題でないため依存を減らした。

| before | after |
| ------------ | ------------- |
| ![unspecified.png](https://qiita-image-store.s3.amazonaws.com/0/5678/3d674395-9ff8-cd19-61a5-f5eda1f1b933.png) | ![unspecified.png](https://qiita-image-store.s3.amazonaws.com/0/5678/2031bca0-544a-b57d-878e-cc3dc3ba70ee.png) |

私は Target 複数にしたことなかったのでハマったが、界隈では常識なのかな... :rolling_eyes: 

## 2. [CocoaPods](https://cocoapods.org/) から [Carthage](https://github.com/Carthage/Carthage) に移行しソース管理に含める

今まで [CocoaPods](https://cocoapods.org/) を使っていたのを [Carthage](https://github.com/Carthage/Carthage) に移行し、ライブラリをソース管理するようにした。
やり方はググれば出てくるのでここでは書かないが、 Cartfile にライブラリを移して地道に Podfile から消していった。 

また、 [CocoaPods を \.gitignore すべきか](http://qiita.com/star__hoshi/items/1f06a58275cdd0b6e450) の理由で Pods/* を gitignore してたが、 git 管理するように変更した。
Clone やコンパイルの時間が必要なくなるので CircleCI のビルドが早くなったり、 clone 後にライブラリのインストールが不要になる。

### 残課題

`R.swift` や `Fabric`, `Crashlytics` は Carthage 未対応だったのでそのまま [CocoaPods](https://cocoapods.org/) に残してある。

また、 [\[iOS\] アプリの設定画面にバージョン表記と謝辞を自動で設定する](http://dev.classmethod.jp/smartphone/iphone/settings-bundle-acknowledgements/) のやり方でライブラリの権利情報など載せていたが、 Carthage だとそれができない。

下記を参考になんとかする予定。

* [Carthageでinstallしたライブラリのライセンス一覧をplistで生成する \- Qiita](http://qiita.com/hatuyuki4/items/feb6bead56b8006520d1)
* [iOSアプリのライセンス一覧のplistを半自動生成して設定画面にキレイに表示するプログラムをGo言語で作った \- Qiita](http://qiita.com/mono0926/items/973752b69c881e00c507)

## 3. 地道にソースコードを改善

[コンパイルに時間がかかるSwiftのコードへWarningを発生させる](http://qiita.com/kazuhiro4949/items/62cedebd08ed87b02333) のやり方でビルド時間を測定。

![unspecified.png](https://qiita-image-store.s3.amazonaws.com/0/5678/2c6ac4c2-af3a-ef59-17cd-09afb0ef7a22.png)

この設定でコンパイル時間が 100ms を超えるメソッドに warning が出るようになった。

ただし、 100ms 以上かかっているメソッドが 1 つかなく、大してやることはなかった。
閾値を 50ms にして確認したところ、幾つか出てきたので「これはどうしようもないやろ...」というの以外は直した。

### 直した一例

Cookie を保存する処理だが、無駄に for 分で回しててしかも `??` を使っていたので修正し 100ms 以下にした。

```diff
-        var targetCookies: [HTTPCookie] = []
-        for cookie in HTTPCookieStorage.shared.cookies ?? [] {
-            targetCookies.append(cookie)
+        guard let cookies = HTTPCookieStorage.shared.cookies else {
+            return
         }
-        let cookiesData = NSKeyedArchiver.archivedData(withRootObject: targetCookies)
+        let cookiesData = NSKeyedArchiver.archivedData(withRootObject: cookies)
```

# 高速化の結果


| before | after |
| ------------ | ------------- |
| ![unspecified.png](https://qiita-image-store.s3.amazonaws.com/0/5678/99870297-3e16-f08e-4618-36c434c7171c.png) | ![unspecified.png](https://qiita-image-store.s3.amazonaws.com/0/5678/2a7b5788-a137-4f51-8434-e267941925b0.png) |
| :clock: 約30秒 | ![unspecified.png](https://qiita-image-store.s3.amazonaws.com/0/5678/e87bb12b-4092-67c5-7982-58929433c685.png) |

CircleCI のビルド + テストに10分以上かかっていたのが4分以下に :tada:
ローカルマシンビルドも4秒以下に :blush: 


### これ以上は...?

iOS エンジニアだけ他の人より良いマシンを要求するのは申し訳ないのだが、
ローカルマシンの馬力が開発効率と直結するから良いマシンが欲しい... :eyes: 

# 最後に

Classi 株式会社ではエンジニアを募集しています。
iOS エンジニア自分しかいなくて寂しいので興味ある人は話を聞きに来てください！！！！！

[日本の教育を変える！先生・生徒向けアプリを作るiOSエンジニア募集！ \- Classi株式会社のモバイルエンジニア中途の求人 \- Wantedly](https://www.wantedly.com/projects/43193)