下記ブログの転載です!
https://rc-code.info/ios/post-273/
iOSにて、UIScrollView
のカクつきを改善する必要があったので備忘録。
今回は UIScrollViewDelegate.scrollViewDidScroll
を利用している時の対処法を紹介します。
UIScrollViewがカクつく理由
まず UIScrollView に限らず描画がカクつく場合、その理由は mainthread
を圧迫していることがほとんどです。
mainthread
については、こちらを参考にしていただければと思います。
scrollViewDidScroll内の処理を見直す
さて、今回解決したいのは UIScrollView
のカクつきですが、scrollViewDidScroll
という UIScrollView.Delegate
の処理が重たくなりすぎているというケースがよく見られます。
後述する対策は scrollViewDidScroll
だけでなく、他の UIScrollView.Delegate
の関数にも有効な場合があるので、参考にしてみてください。
UIScrollView のカクつきを再現する負荷試験
下記は extension
で scrollViewDidScroll
を定義したサンプルになります。
extension ViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
var count: Int = 0
var array: [Int] = []
repeat {
array.append(count)
array.reverse()
count += 1
} while(count < 200)
}
}
上記のソースでは、スクロールされる度に 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をスクロールしていただくと、カクつくことが分かると思います。
カクつきを解消する方法
class ViewController: UIViewController {
//スクロール処理用Queue
private let scrollSessionQueue = DispatchQueue(label: "ScrollSessionQueue")
...以下省略
}
extension ViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// mainthread を圧迫しないように、別スレッドで処理
scrollSessionQueue.async {
var count: Int = 0
var array: [Int] = []
repeat {
array.append(count)
array.reverse()
count += 1
} while(count < 200)
}
}
}
上記のソースでは、ViewController
に scrollLogSessionQueue
という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
検証環境
Mac: 10.14.4
XCode: 10.2
Swift: 5.0