Xcode
小ネタ
timer
Swift
swift4

Swift4環境でTimerの使い方と挙動を確認してみた

はじめに

こんにちは:leaves:
アプリ内で利用する機会はそれほどでもないのですが、定期実行するTimerについて調べてみたのでメモとして書いてみたいと思います。(Xcode: 9.1, Swift:4.0.2)
至らぬ点などコメント頂けたら幸いです。

Timerの基本的な利用

Timerはインスタンス生成即座に起動されるようです。
:warning:ここで注意点なのですが「fire() = 起動」を意味するものではなく、fire()は、Timerインスタンスが破棄されていない状態にて、すぐにスケジューリングしていたメソッドを呼びたい時に使うためのメソッドのようです。

import UIKit

class MainViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(MainViewController.timerUpdate), userInfo: nil, repeats: true)
    }

    @objc func timerUpdate() {
        print("update")
    }
}
//5秒間隔で呼ばれる
//5秒後...
//update
//5秒後...
//update
名前 説明
timeInterval ループなら間隔,1度きりなら発動までの秒数
target メソッドを持つオブジェクト
selector 実行するメソッド
userInfo オブジェクトに付ける値
repeats 繰り返し実行するかどうか

Timerをtargetの変数(ここではMainViewController)に保持した場合などの色々なパターンを書いてみたいと思います。

scheduledTimerを利用するパターン

scheduledTimerでTimerを生成した場合は、内部的に現在の実行ループ(通常はメインスレッド)にてスケジュール登録されるようです。

オブジェクトを保持しない

import UIKit

class MainViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        Timer.scheduledTimer(timeInterval: 5,                                         //ループなら間隔 1度きりなら発動までの秒数
                                target: self,                                         //メソッドを持つオブジェクト
                                selector: #selector(MainViewController.timerUpdate),  //実行するメソッド
                                userInfo: nil,                                        //オブジェクトに付けて送信する値
                                repeats: true)                                        //繰り返し実行するかどうか
    }

    @objc func timerUpdate() {
        print("update")
    }
}
//5秒間隔で呼ばれる
//5秒後...
//update
//5秒後...
//update

オブジェクトを保持する

import UIKit

class MainViewController: UIViewController {

    var timer: Timer?

    override func viewDidLoad() {
        super.viewDidLoad()
        self.timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(MainViewController.timerUpdate), userInfo: nil, repeats: true)
    }

    @objc func timerUpdate() {
        print("update")
    }

    @IBAction func buttonAction(_ sender: Any) {
        print("invalidate!!")
        self.timer?.invalidate()
    }
}
//5秒間隔で呼ばれる
//5秒後...
//update
//5秒後...
//update
//invalidate!!
//.......

変数を保持しておくことでinvalidate()でタイマーを破棄することができ、実質タイマーが停止します。
:warning:Timerインスタンスは破棄されるので、同じインスタンスでの再開は不可能です。再開する場合は破棄した時点の時間を計算するなど、Timerインスタンスを生成し直す必要があります。


変数に対して、重ねてscheduledTimerをした場合

import UIKit

class MainViewController: UIViewController {

    var timer: Timer?

    override func viewDidLoad() {
        super.viewDidLoad()
        self.timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(MainViewController.timerUpdate), userInfo: nil, repeats: true)
        print(self.timer)
        self.timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(MainViewController.timerUpdate2), userInfo: nil, repeats: true)
        print(self.timer)
    }

    @objc func timerUpdate() {
        print("update")
    }
    @objc func timerUpdate2() {
        print("update2")
    }

    @IBAction func buttonAction(_ sender: Any) {
        print("invalidate!!")
        self.timer?.invalidate()
    }
}
//Optional(<__NSCFTimer: 0x1c017c500>)
//Optional(<__NSCFTimer: 0x1c017e600>)
//5秒後...
//update
//update2
//5秒後...
//update
//update2
//invalidate!!
//5秒後...
//update
//5秒後...
//update
//invalidate!!
//5秒後...
//update

内部的に実行ループに登録されるようで、2回目の上書きでも、invalidate後に1回目で生成したTimerが呼び出されてしまいます。

Timer.initを利用するパターン

イニシャライザでTimerを生成した場合は、自ら実行ループ(通常はメインスレッド)にスケジュール登録をする必要があります。

import UIKit

class MainViewController: UIViewController {

    var timer: Timer?

    override func viewDidLoad() {
        super.viewDidLoad()
        self.timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(MainViewController.timerUpdate), userInfo: nil, repeats: true)
        //登録
        RunLoop.main.add(self.timer!, forMode: .defaultRunLoopMode)
    }

    @objc func timerUpdate() {
        print("update")
    }

    @IBAction func buttonAction(_ sender: Any) {
        print("invalidate!!")
        self.timer?.invalidate()
    }
}
//5秒間隔で呼ばれる
//5秒後...
//update
//5秒後...
//update
//invalidate!!
//.......

変数に対して、重ねてinitをした場合

import UIKit

class MainViewController: UIViewController {

    var timer: Timer?

    override func viewDidLoad() {
        super.viewDidLoad()
        self.timer = Timer(timeInterval: 5, target: self, selector: #selector(MainViewController.timerUpdate), userInfo: nil, repeats: true)
        self.timer = Timer(timeInterval: 5, target: self, selector: #selector(MainViewController.timerUpdate2), userInfo: nil, repeats: true)
        RunLoop.main.add(self.timer!, forMode: .defaultRunLoopMode)
    }

    @objc func timerUpdate() {
        print("update")
    }
    @objc func timerUpdate2() {
        print("update2")
    }

    @IBAction func buttonAction(_ sender: Any) {
        print("invalidate!!")
        self.timer?.invalidate()
    }
}
//5秒間隔で呼ばれる
//5秒後...
//update2
//5秒後...
//update2
//invalidate!!
//.......

こちらは上書きされた後に自らケジュール登録をしたので、2回目のTimerのみが呼ばれます。

タイマーが呼ばれているタイミング

iPhone

ホームボタンを押して、アプリがバックグランドになると、タイマーのメソッド呼び出しが止まり、再びアクティブになると呼び出しが戻るようです。

AppleWatch

digital crownを押してHomeに戻っても、Timerは呼ばれ続けるようです。任意で止めたい場合はwillActivate()didDeactivate()でコントロールする必要があるみたいです。

参考にさせていただいた記事

見て頂いてありがとうございます。