[iOSパフォーマンスチューニング]スクロールがカクつく場合の対処

# 概要
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」**となっていることを確認します。なっていない場合は変更してください。

<img width = "400" src="https://qiita-image-store.s3.amazonaws.com/0/113553/102b45ff-31c6-3be0-86fb-03db4a5bec81.png">

## ビルドとTime Profilerの起動
Productタブの「Profile」をクリックしてビルドを開始します。
<img width = "400" src="https://qiita-image-store.s3.amazonaws.com/0/113553/e90bbc44-3491-9631-e3f7-460a112c8648.png">

ビルドが終わったら以下のような選択画面になるので、「Time Profiler」を選択します。
<img width = "400" src="https://qiita-image-store.s3.amazonaws.com/0/113553/3281559a-ebf2-0570-bc8e-88de285bf8bb.png">

## Time Profilerの操作
赤い丸のレコードボタンをクリックして、アプリの起動&計測を開始します。
<img width = "400" src="https://qiita-image-store.s3.amazonaws.com/0/113553/88f37bab-c0b7-e8c0-b479-aef2e6886e24.png">

アプリが起動したら、アプリを操作してスクロールがカクつく画面でスクロール操作を何度か繰り返します。すると以下のように波形が出るので、一時停止ボタンで計測を止めます。
<img width = "400" src="https://qiita-image-store.s3.amazonaws.com/0/113553/fce29d55-a92c-fa46-11b3-50bd98e74fb2.png">

スクロール操作していた部分の波形を以下のようにドラッグして範囲選択します。
<img width = "400" src="https://qiita-image-store.s3.amazonaws.com/0/113553/1dbd4b75-17b5-6f08-11e1-87342c4d57bf.png">

## スタックトレースの確認

画面下のスタックトレースを確認し、Main Threadと書いてあるところの▶︎をボタンをクリックしていくと徐々にトレースの展開ができます。
<img width = "400" src="https://qiita-image-store.s3.amazonaws.com/0/113553/0dc9d74d-7f6b-7b54-3a8b-2c4ef48ca26b.png">

ここでポイントとして画面下の「Call Tree」から**「Hide System Libraries」にチェック**を入れておくと、システム内部のプライベートメソッドを非表示にし、自分の実装したメソッドだけにフィルタできるので見やすくなります。
<img width = "400" src="https://qiita-image-store.s3.amazonaws.com/0/113553/eba2e8da-b8e6-7ade-6437-8f4a81259590.png">

スタックトレースは上から時間のかかっている順になっているのでどんどん展開していきましょう。

以下の例では、UITableViewの高さ計算をする度にモデルの初期化が行われ、さらにplistへのファイルアクセスがされていることが分かりました。
<img width = "400" src="https://qiita-image-store.s3.amazonaws.com/0/113553/327f2240-681f-5704-f08f-7cdafd0c23ce.png">

セルの生成や高さ計算といった処理はUITableViewやUICollectionViewなどでは頻繁に実行される箇所であり、ここに何度も実行すべきでない処理が入っているのは良くありません。これらの処理を見なおすことでパフォーマンスは改善します。

**良くある対処法**

 - 実行タイミングを変更する
 - 別スレッドで実行する
 - AutoLayoutの計算に時間がかかっている場合は固定値や自分で計算する
 - layoutIfNeededを余分に呼ばない
（即時レイアウト計算をする必要がないならsetNeedsLayoutに変更する）
 - キャッシュする


その他の事例としては、高さ計算のために**layoutIfNeededを必要以上に実行**したり、**XIBファイルを何度もロード**している場合、透明部分の描画に**オフスクリーンレンダリング**が発生している場合などがあります。

いずれの原因にせよ、Time Profilerで確認することが近道かと思います。

# 関連記事
 - [UIKitのView表示ライフサイクルを理解する](https://qiita.com/shtnkgm/items/f133f73baaa71172efb2)