掲題の通りこの記事ではアプリの起動時処理と高速化のためのポイントを詳しく書いていきます。
自社のアプリで起動時間に課題意識を持って、起動高速化に取り組んでいる方も多いのではないでしょうか。
そんな時に少しでも参考になれば幸いです。
時間があまりなく改善策だけサクッとまとめて知りたい方は、文末のまとめを見ていただけるといいと思います。
起動時間を計測しよう
まずは現状を確認するためにアプリの起動時間を計測しましょう。
ただ起動時間を確認するだけであればFirebase Performance
だったり、XcodeのOrganizer -> Launch Time
で確認できます。ただやはり起動時間を見直したい場合はもう少し詳しく分析した計測がしたいです。
そんな時はInstrumentsのApp Launchを使用します。
使い方は簡単で、計測したいアプリを選択して、左上の赤いアイコンを押すと計測のレコーディングが開始されます。
勝手にアプリが立ち上がりその後Recording
の文字だけになるので、そうしたら計測が完了しているのでストップしてください。
こんな感じの結果が取得できます。
これで計測自体は完了です。では計測結果について詳しく解説をつけながら見ていきます。
起動時の処理
起動時の処理は大きく2つに分かれています。グラフでもわかるように紫の部分と緑の部分です。
すごく簡単にいうと紫の部分はコード以外のシステムの初期化処理を行っている時間です。一方で緑の部分はコードで実装した部分のアプリの初期化処理をしている時間です。
この先の情報は基本的にOptimizing App Launchを参考にしたものです。
では詳しく中身を見ていきましょう。
Initializing
ここで先に謝っておかなければならないことがあります。掲題に起動高速化の全てと書いてありますが、実はこの部分だけ絶対これ!という解答を持っていません。。
下部参考のドキュメントを読みましたが、特にそれらしい情報を見つけることはできませんでした。
ここからはあくまで推測ですが、低レイヤーの処理を行なっていて、外部から操作できることはないのかなと思っています。
もし詳しい情報を知ってる方がいればコメント頂けると嬉しいです。
System Interface Initialization
処理は大きくdyld
とlibSystemInit init
があります。
dyld
dyld
ではリソースの解析や共有ライブラリやフレームワークのロードを行っています。
改善策
不要な実装を削除すること・dynamic library
からstatic library
へ変更することが非常に有効です。
不要な実装をあぶり出すのは、私はperipheryを使用しました。類似のサービスもいくつか見ましたが、一番精度が良く思いました。
一番感動したのは、呼び出し元がないメソッドのみが使用しているメソッドも不要コードとして検出できていたことです。
しかし、やはり消す時に手動のチェックは必要でした。完璧に不要コードを検出するのは難しいですね。
static library
への変換はCocoaPodsで扱っていたライブラリをSwiftPMへ移行しました。
libSystemInit init
libSystemInit init
ではアプリケーション内の低レベルのシステムコンポーネントの初期化をしています。
これはほとんどシステム側の作業で、コストも決まっています。そのため特段気にしなくて良いです。
ここまでで静的な実行時の初期化が完了しました。
Static Runtime Initialization
System Interface Initialization
までとは異り、ここではObjective-CとSwiftのランタイム初期化を行います。
コード内の静的な初期化メソッドはここで実行されています。一般的に初期化メソッドを必要としない限り、アプリはここでは何もすべきではありません。
改善策
静的な初期化メソッドがどのようなものかというと、Objective-Cの+load
メソッドの読み込み、__attribute__((constructor))
の付与されたメソッドなどが値します。
このような処理が存在する場合は、その処理が本当に必要なのか、遅延実行はできないのか検討してみてください。
UIKit Initialization
これでついに緑の部分に入ります。
UIKit InitializationではUIApplication
とUIApplicationDelegate
をインスタンス化しています。
特にUIApplication
をサブクラス化したり、UIApplicationDelegate
の初期化で何かしているとここの部分に影響が出ます。
Application Initialization
緑の部分でエンジニアが気をつけなければいけないのは、主にこの部分です。
アプリを起動した時には必ずdidFinishLaunchingwithOptions
が呼ばれますが、didFinishLaunchingwithOptions
で実装されている処理はApplication Initializationに該当します。他にもAppDelegateやScenedelegateのメソッドはApplication Initializationで行われます。
改善策
ここにおける改善策はAppDelegate・Scenedelegate共通で、必要のない処理は遅延実行させましょう。
例えば必要のないライブラリの初期化は、didFinishLaunchingwithOptions
で行うのではなく、そのライブラリが初めて使用されるときに行うといいです。
// SampleLibraryというライブラリを使うとする
// SampleLibraryを使用するラッパーを作る
class SampleLibraryClient {
static let shared = SampleLibraryClient()
init() {
// ここでライブラリの初期化を行う
}
}
また私はまだ実測していないので確証はないですが、UISceneを導入していない場合は導入すると、起動時のライフサイクルが変わり効果があるようです。
Initial Frame Rendering
Application Initializationでアプリの起動準備は完了しましたので、次はフレームのレンダリングになります。
フレームのレンダリングでは、ビューの作成、レイアウトの作成、そして描画をおこなっています。複雑なレイアウトであれば、もちろんその分時間がかかります。
改善策
最初に表示されるViewをシンプルにすることがポイントになります。具体的には、以下のようなことが効果的です。
- Viewの階層を減らしてなるべくフラットなビューにすること
- ViewのAuto Layoutの制約を減らすこと
- 起動中に表示させる必要のないViewは遅延して表示させること
これでアプリ起動時の一連の流れと、各フェーズでできる改善策は終了となります。
まとめ
起動時間の高速化のためにできることの一覧です。
- 使われていない実装やリソースの削除する
- ライブラリを
dynamic library
をstatic library
へ移行する - コード内の静的な初期化メソッドを無くす
- AppDelegate(Scenedelegate)で必要のない処理は遅延実行させる
- 最初に表示するViewの階層を減らす
- ViewのAuto Layoutの制約を減らす
- 起動中に表示させる必要のないViewは遅延して表示させる
各フェーズでどのくらい改善できたかなど、具体的な数字はまた別途ブログで書いていこうかなと思います。
その際はこちらの記事にもリンクを貼りますね。
追記:現在の進捗を公開しました!
iOSアプリ起動高速化に挑戦!不要コードやリソースの見直しとライブラリのstatic化編