概要
UIScrollView、UITableView、UICollectionViewなどのスクロール操作を行う画面で、スクロールがカクつく、滑らかにスクロールできないという問題はよくある事象です。ただ動くアプリをつくるだけでなく、そのパフォーマンスを改善するための基本的な方法について説明します。
⏳時間がない人向けの要点まとめ
- パフォーマンスチューニングはTimeProfilerでのメインスレッド調査が最短ルート
- dSYM生成しないとスタックトレース解析できないので注意
- システム側のプライベートメソッドは非表示にするとスタックトレースが見やすくなる
スクロールのパフォーマンスが落ちる根本原因
スクロールのパフォーマンスが落ちてしまう根本原因はほとんど「メインスレッドのブロック」にあります。これはiOSだけでなく、Androidアプリでも共通かと思います。
iOSの仕様上、画面レイアウトや描画などはメインスレッドで実行されます。もしも通信処理やファイルやDBアクセス、重たい計算などをメインスレッドで実行してしまった場合、その間メインスレッドはその重たい処理に占有されてしまいます。つまり、その間はスクロールが実行されてもメインスレッドはブロックされているため、OSは断続的にしか描画ができないことになります。
この根本原因を理解した上で、どのように対処すればよいでしょうか?
まずは何と言っても現状把握
まずは現状把握をし、どこの処理が重たいのかをしっかりと確認してから対処すべきです。これまでの経験則で当てずっぽうに修正をしていくのは逆に時間がかかるケースが多いです。
Time Profilerで計測をする
XcodeにはTime Profilerという優秀なパフォーマンスチューニングツールが入っています。
Time Profilerでは、スレッド毎にスタックトレースが表示でき、どのメソッドに時間がかかったのか定量的に把握することができます。
前準備(dSYMファイルを生成)
通常、Swiftコードのスタックトレースはアドレス表記となっており。クラス名やメソッド名を見ることはできません。アドレス値を元の名前に戻すには、ビルド時にシンボル情報(dSYM)を出力する必要があります。
ビルド設定で「format」と検索すると、「Debug Information Format」という項目がありますので、Debugコンフィグ時の設定が**「DWARF with dSYM File」**となっていることを確認します。なっていない場合は変更してください。

ビルドとTime Profilerの起動
Productタブの「Profile」をクリックしてビルドを開始します。
ビルドが終わったら以下のような選択画面になるので、「Time Profiler」を選択します。
Time Profilerの操作
赤い丸のレコードボタンをクリックして、アプリの起動&計測を開始します。
アプリが起動したら、アプリを操作してスクロールがカクつく画面でスクロール操作を何度か繰り返します。すると以下のように波形が出るので、一時停止ボタンで計測を止めます。
スクロール操作していた部分の波形を以下のようにドラッグして範囲選択します。
スタックトレースの確認
画面下のスタックトレースを確認し、Main Threadと書いてあるところの▶︎をボタンをクリックしていくと徐々にトレースの展開ができます。
ここでポイントとして画面下の「Call Tree」から**「Hide System Libraries」にチェック**を入れておくと、システム内部のプライベートメソッドを非表示にし、自分の実装したメソッドだけにフィルタできるので見やすくなります。
スタックトレースは上から時間のかかっている順になっているのでどんどん展開していきましょう。
以下の例では、UITableViewの高さ計算をする度にモデルの初期化が行われ、さらにplistへのファイルアクセスがされていることが分かりました。
セルの生成や高さ計算といった処理はUITableViewやUICollectionViewなどでは頻繁に実行される箇所であり、ここに何度も実行すべきでない処理が入っているのは良くありません。これらの処理を見なおすことでパフォーマンスは改善します。
良くある対処法
- 実行タイミングを変更する
- 別スレッドで実行する
- AutoLayoutの計算に時間がかかっている場合は固定値や自分で計算する
- layoutIfNeededを余分に呼ばない
(即時レイアウト計算をする必要がないならsetNeedsLayoutに変更する) - キャッシュする
その他の事例としては、高さ計算のためにlayoutIfNeededを必要以上に実行したり、XIBファイルを何度もロードしている場合、透明部分の描画にオフスクリーンレンダリングが発生している場合などがあります。
いずれの原因にせよ、Time Profilerで確認することが近道かと思います。