【Swift】Timerがズレる (遅延する) 時の原因と対処法

【Swift】Timerがズレる (遅延する) 時の原因と対処法

iOSにて、UIScrollViewTimer を併用した際に Timer がずれてしまったので対応方法を備忘録。
今回は TimerscheduledTime関数 を利用した際の対応法をお伝えします。
 

Timer.scheduledTime関数 の使い方

Timer.scheduledTime関数 の基本的な使い方は下記にまとめましたので、ご参考ください。
【Swift】加算型Timer(ストップウォッチみたいな)の作り方 〜Timerの基本的な使い方〜
 

Timer がズレる (遅延する) 環境と原因

Timer がずれる環境ですが、操作関連の処理が走っている時に発生しやすいです。
僕の場合、UIScrollViewscrollViewDidScroll イベントが発火するタイミング、つまりスクロールしている場合にこの現象が起きました。

この現象が起こる原因ですが、Timer.scheduledTime関数 を実行して作られる Timer画面操作の処理と同様モードとして RunLoop に設定されるからです。
Timer.scheduledTime関数Timer を作成し、その実行threadを RunRoopdefaultMode として自動的に登録します。

コードで書くとこんな感じです。

RunLoop は入力系(NSConnecionや画面操作など)の thread を処理していく機構です。

上記のコードで、forMode.default と指定していますが、これは NSConnection 以外の入力系を処理する際に推奨・利用されるModeです。
つまり、操作系の入力と同様のイベントとして処理されるということです。

これが原因で、Timer が遅延します。
操作系の入力と同様に処理しているので、正確に Timer の実行処理をしたいのに、そちらの処理に時間をとられた結果 Timer の実行が遅れるわけです。
 

Timer がズレる (遅延する) 時の対処法

上記の通り、原因は Timer が操作系と同様のModeとして RunLoop に登録されているからです。
なので、遅延が起きた時は明示的に下記のように .common モードとして登録する必要があります。

.common Modeは 一般的な利用目的がある際に設定するモード らしいです、(ようわからんw)
つまりアプリ内で必要に駆られて利用する場合(他モードのモーダルパネルやコネクション、操作のトラッキングを省く)に使えという事だと思います。

こちらを設定すれば、操作系との差別化が図られ、正常に動作すると思われますのでお試しください。
 

RunLoop操作時の注意点

RunLoopTimer を登録すると強参照になります。
なので、プロパティとして Timer を持つ際は weak にしておくべきです。

また、公式ドキュメントにもあるように RunLoopthreadSafe なオブジェクトではありません
thread管理も同様に行なっている場合、予期せぬ強制終了などが起こり得るので、注意して実装しましょう!
 

参考ドキュメント

公式RunLoopドキュメント
公式RunLoop.Modeドキュメント
公式ThreadProgramingGuide