下記ブログの転載です
https://rc-code.info/ios/post-172/
iOSにて、UIScrollView
と Timer
を併用した際に Timer
がずれてしまったので対応方法を備忘録。
今回は Timer
の scheduledTime関数
を利用した際の対応法をお伝えします。
Timer.scheduledTime関数 の使い方
Timer.scheduledTime関数
の基本的な使い方は下記にまとめましたので、ご参考ください。
【Swift】加算型Timer(ストップウォッチみたいな)の作り方 〜Timerの基本的な使い方〜
Timer がズレる (遅延する) 環境と原因
Timer
がずれる環境ですが、操作関連の処理が走っている時に発生しやすいです。
僕の場合、UIScrollView
の scrollViewDidScroll
イベントが発火するタイミング、つまりスクロールしている場合にこの現象が起きました。
この現象が起こる原因ですが、Timer.scheduledTime関数
を実行して作られる Timer
が画面操作の処理と同様モードとして RunLoop
に設定されるからです。
Timer.scheduledTime関数
は Timer
を作成し、その実行threadを RunRoop
に defaultMode
として自動的に登録します。
コードで書くとこんな感じです。
RunLoop.current.add(timer, forMode: .default)
RunLoop
は入力系(NSConnecionや画面操作など)の thread を処理していく機構です。
上記のコードで、forMode
に .default
と指定していますが、これは NSConnection
以外の入力系を処理する際に推奨・利用されるModeです。
つまり、操作系の入力と同様のイベントとして処理されるということです。
これが原因で、Timer
が遅延します。
操作系の入力と同様に処理しているので、正確に Timer
の実行処理をしたいのに、そちらの処理に時間をとられた結果 Timer
の実行が遅れるわけです。
Timer がズレる (遅延する) 時の対処法
上記の通り、原因は Timer
が操作系と同様のModeとして RunLoop
に登録されているからです。
なので、遅延が起きた時は明示的に下記のように .common
モードとして登録する必要があります。
RunLoop.current.add(timer, forMode: .common)
.common
Modeは 一般的な利用目的がある際に設定するモード らしいです、(ようわからんw)
つまりアプリ内で必要に駆られて利用する場合(他モードのモーダルパネルやコネクション、操作のトラッキングを省く)に使えという事だと思います。
こちらを設定すれば、操作系との差別化が図られ、正常に動作すると思われますのでお試しください。
RunLoop操作時の注意点
RunLoop
へ Timer
を登録すると強参照になります。
なので、プロパティとして Timer
を持つ際は weak
にしておくべきです。
また、公式ドキュメントにもあるように RunLoop
は threadSafe
なオブジェクトではありません。
thread管理も同様に行なっている場合、予期せぬ強制終了などが起こり得るので、注意して実装しましょう!
参考ドキュメント
公式RunLoopドキュメント
公式RunLoop.Modeドキュメント
公式ThreadProgramingGuide