しおメモ

雑多な技術系ブログです。ニッチな内容が多いです。

Xcodeのビルド時間をキレずに改善する

Xcode10でNew Build Systemになってから、全体的にビルド時間は短くなりました。

しかし、それでも諸々の原因によりビルドがめちゃくちゃ遅い場合があります。
そんな場合でも、キレずに冷静に改善する方法紹介します。

ビルド時間の計測方法

Xcodeの上部にビルド時間を表示するために、ターミナルで

defaults write com.apple.dt.Xcode ShowBuildOperationDuration YES

を実行します。

f:id:scior:20190725231154p:plain

ステップごとのビルド時間を計測するには、Product > Perform Action > Build With Timing Summaryを実行します。

f:id:scior:20190725230902p:plain

xcodebuildの場合は、-showBuildTimingSummaryをつけます。

xcodebuild clean build -project ColorStock.xcodeproj -scheme ColorStock -destination="platform=iOS Simulator,name=iPhone 8" -showBuildTimingSummary

出力はこのようになります。

Build Timing Summary

CompileSwiftSources (1 task) | 5.000 seconds

PhaseScriptExecution (1 task) | 3.000 seconds

CompileStoryboard (2 tasks) | 2.000 seconds

...

** BUILD SUCCEEDED **

大まかにどこに時間がかかっているかがわかります。
まんべんなくかかってたら諦めるしか無い

ビルド設定の見直し

New Build Systemの利用

Xcode10から新しいビルドシステムが採用されているので、そちらを利用します。

f:id:scior:20190725231014p:plain

旧来のビルドシステムとは違い、並列ビルドをしてくれるので、IDEBuildOperationMaxNumberOfConcurrentCompileTasksの指定は不要になります。
こちらをすでに設定している場合、そちらが優先されてしまうので、無効にしておきます。

defaults delete com.apple.dt.Xcode IDEBuildOperationMaxNumberOfConcurrentCompileTasks

CocoaPodsのライブラリも並列ビルドしてくれます。

最適化の設定変更

最適化はデバッグ時には必要ないので、Optimization Levelは-O0にします。

f:id:scior:20190725230943p:plain

ライブラリ管理の見直し

CocoaPodsからCarthageに移行する

CocoaPodsを使っている場合、Carthageに切り替えることで、ライブラリのfetchと同時にframeworkのビルドを行ってくれるため、その分の時間を節約できます。

特に、クリーンビルドの場合、CocoaPodsで導入したライブラリは、再度ビルドし直しになってしまうので、かなり時間がかかってしまいます。

CocoaPodsのプレビルド

ライブラリが多すぎて断念しましたが、Carthageが使えないライブラリの場合、Pods.xcodeprojを別にビルドして、framework化してリンクする方法もあります。

未使用コードやアセットの削除

未使用コードの削除

peripheryというOSSで、Swift限定ですが、使われていないコードを検知することが出来ます。

github.com

複雑な書き方をしている部分は誤検出が多々あるので、一つずつCall Hierarchyやシンボル検索でたどっていくことをおすすめします。

未使用アセットの削除

こちらもFengNiaoというOSSで検知することができます。

github.com

やはりこちらも誤検出があるので、確認が必要です。
バイナリサイズも小さくなります。

コードの書き方の改善

Swiftは言語として非常に高機能ですが、便利な機能とトレードオフでビルド時間が増加することがあります。
とくに、型推論は時間がかかることが多く、演算の途中では型が確定しないような書き方をすると遅くなることがあります。

CompileSwiftに時間が取られている場合、ここを改善することで多少ビルド時間が短くなることがあります。

メソッドごとのビルド時間の計測

メソッドごとのビルド時間を測るには、ビルドオプションのOther Swift Flagsに-Xfrontend -debug-time-function-bodiesを追加します。

f:id:scior:20190725232351p:plain

xcodebuildの場合、OTHER_SWIFT_FLAGS="-Xfrontend -debug-time-function-bodies"で計測することが出来ます。

計測した時間を集計するには、xcactivitylogから拾ってくるか、xcodebuildを使う必要があります。

👇測定用のPythonスクリプトの例です https://github.com/Scior/SBTProfiler/blob/master/src/profiler.py

👹シェル芸 scior.hatenablog.com

コンパイルが長くなるケース

細かいケースは割愛しますが、配列やString+結合や、nil coalescing operator、genericsなどはコンパイルが長くなる場合があります。

以下、遅くなるようなコードの例です。

Stringの+結合

let hoge: String? = "hoge"
let fuga: String? = "fuga"

let result = "Hello, " + (hoge ?? "") + "and " + (fuga ?? "")

特にnil coalescing operatorと組み合わせると遅くなるので、一気に結合しないほうがよいです。

CGFloatの演算

let height = (hogeHeight - inset * 2) / 2

let height: CGFloatのようにあらかじめ型を明示することで改善することが多いです。

mapなどのgenericメソッド

let array = [1 ,3 ,7, 9]

let result = array.map { num in
    // 複雑な処理
}

num -> Hogeのように戻り値の型明示すると改善します。

そもそもビルドしない

最近良くやりますが、ちょっとの修正の場合、毎回ビルドしないで、LLDBで書き換える方法もあります。

scior.hatenablog.com

万策尽きたら

28コアのMac Proを買えばいいんじゃないかな🤔

www.apple.com