タイマー(Timerクラス)

macOS Swift

タイマー(Timerクラス)

macOS 10.15.7 / Xcode 11.3.1 / Swift 5.0

概要

Timerオブジェクトは、指定した処理を定間隔に起動するときに利用するなど、適用範囲の広い汎用的な機能で、実装も手軽だ。 利用する機会はかなり多いと思われるが、使用方法で注意しなければいけない点もある。

方式1(強参照パターン)

1秒間隔に時刻を printする処理をごく一般的な方法でコーディングした。#selector を使うのはクラシックな技法ではある。
留意しておくことは、このコードでは、処理の途中で MyTestオブジェクトを解放しようとしてもできないことである。原因は Timerオブジェクトが MyTestオブジェクトを強参照で保持しているからである(scheduledTimerメソッドの target引数に selfを指定している)。Timerオブジェクトは Runloopに追加されるので、Runloopが続く限り、Timerオブジェクトに強参照される MyTestオブジェクトは解放されないことになる。
なお、オブジェクトの解放は、オブジェクトを保持する変数に nilを代入すればよい。これにより ARCが参照カウントをデクリメントし、ゼロになった時点でオブジェクトが自動的に解放される。

方式2(弱参照パターン)

このような操作をする場面はあまり想定できないが、一応、回避策を紹介しておこう。Timerオブジェクトが 保持する MyTestオブジェクトを弱参照にするというものである。
scheduledTimerメソッドでは、タイマーで起動する処理はブロック文に記述する(モダンな方式)。ブロック文の先頭に [weak self] と属性を記述すれば、selfは 弱参照として扱われる。これにより MyTestオブジェクトが解放されるとき、Timerオブジェクトが自身を離さないという状態を回避することができる。
オブジェクトが解放される直前に起動される deinitメソッドにタイマーを無効にする invalidateメソッドを実行する。これは、タイマーの起動を停止し、Timerオブジェクトを Runloopから取り除くものである。
この方式は、標準的で抜けのない対応になっているので、タイマー処理の実装では、これを定型として利用していくことが望ましいかもしれない。
[ 補足 ]
上記でも触れたが、Timerクラスの invalidateメソッドは Timerオブジェクトを Runloopから取り除く。従って、このメソッドをどこかで実行すれば、その時点で Timerオブジェクトは解放された状態になるので、例え MyTestオブジェクトが強参照されていたとしても、オブジェクト解放は問題なくできる。アプリケーションの作りにもよるが、これもひとつの方法だろう。