【Swift】ScrollViewがカクつく時の対処法

【Swift】ScrollViewがカクつく時の対処法

iOSにて、UIScrollView のカクつきを改善する必要があったので備忘録。
今回は UIScrollViewDelegate.scrollViewDidScroll を利用している時の対処法を紹介します。
 

UIScrollViewがカクつく理由

まず UIScrollView に限らず描画がカクつく場合、その理由は mainthread を圧迫していることがほとんどです。

mainthread については、こちらを参考にしていただければと思います。

scrollViewDidScroll内の処理を見直す

さて、今回解決したいのは UIScrollView のカクつきですが、scrollViewDidScroll という UIScrollView.Delegate の処理が重たくなりすぎているというケースがよく見られます。

後述する対策は scrollViewDidScroll だけでなく、他の UIScrollView.Delegate の関数にも有効な場合があるので、参考にしてみてください。

UIScrollView のカクつきを再現する負荷試験

下記は extensionscrollViewDidScroll を定義したサンプルになります。

上記のソースでは、スクロールされる度に 0〜199 までの数字を配列に格納してはリバースするという再帰処理を行なっています。

count を array に格納しリバース、countに+1 (array=[0]、count=1 になる)
count を array に格納しリバース、countに+1 (array=[1,0]、count=2 になる)
  以下、199まで繰り返し、、、

この処理が少しでもスクロールされる度に行われるので、なかなか重いタスクとなります。
scrollViewDidScroll という関数は、スクロール というUI操作に紐づいているので、この重たい処理は mainthread にて処理されてしまいます。
つまり、scrollViewDidScroll 関数内に負荷がかかると画面更新に影響するということです。
実際にこの処理を加えてUIScrollViewをスクロールしていただくと、カクつくことが分かると思います。

カクつきを解消する方法

上記のソースでは、ViewControllerscrollLogSessionQueue というQueueを新たに呼び出しています。

そして scrollViewDidScroll 内の処理を scrollSessionQueue.async で囲むことで、処理を mainthread から別threadに移譲しています。
すなわち、処理は mainthread ではなく、UIに関連しない別の thread にて処理されることになります。

実際に動かしてみると、スクロールが滑らかになっていることが分かると思います。
DispatchQueue の詳しい生成方法については こちら を参考にしていただければと思います。

修正後の注意点

ただ、別threadにて処理を行う際には注意が必要です。
主に挙げられる注意点は

①別threadにすれば100%画面更新に影響がなくなるわけではない
②別threadに移譲するので、実行タイミングがズレる

という2点です。

①についてですが、 scrollLogSessionQueue.async で別スレッドを使ってもCPUやGPUが圧迫されることは免れませんし、別スレッド内の処理によっては再度 mainthread への移譲が行われるという事もあり得ます。
なので100%影響がなくなるというわけではありません。
サンプルコード scrollLogSessionQueue.async の中で行われている処理の負荷を上げていくと、この事が分かるかと思います。

②について、指定したthreadに処理を移譲するので、当然ながら処理は画面と同期しません。
画面的にはスムーズにスクロールしたり、スクロールが既に完了していても、scrollLogSessionQueue.async 内の処理が同様に完了しているとは限らないので、注意が必要です。
 

検証Playground

検証Playground Git
 

検証環境

Mac: 10.14.4
XCode: 10.2
Swift: 5.0
 

参考ドキュメント

公式 DispatchQueue ドキュメント
公式 UIScrollView ドキュメント