LoginSignup
7
8

More than 5 years have passed since last update.

【Swift】加算型Timer(ストップウォッチみたいな)の作り方 〜Timerの基本的な使い方〜

Posted at

下記ブログの転載です
https://rc-code.info/ios/post-167/

Swiftにてストップウォッチの様な 加算型のTimer を作りたかったので、備忘録。
今回は TimerscheduledTime関数 を利用して、加算型Timerを作成します。

Timer.scheduledTimer のサンプル

まずは先に全体のサンプルを。。

final class ViewController {

    private var timer: Timer?                           // Timerを保持する変数
    private(set) var timerTotalDuration: TimeInterval = 0    // 総秒数を保持する変数

    func startTimer() {
        // ⑤ Timer のスケジューリング重複を回避
        guard timer == nil else { return }

        // ① Timerのスケジューリングと保持
        timer = Timer.scheduledTimer(
            timeInterval: 5,
            target: self,
            selector: #selector(handleTimer(_:)),
            userInfo: nil,
            repeats: true
        )
    }

    func stopTimer() {
        // ③ Timer のスケジューリングを破棄
        timer?.invalidate()
    }

    func resetTimer() {
        // ④ Timer のスケジューリングを破棄し、総秒数を0に戻す 
        stopTimer()
        timerTotalDuration = 0
    }

    @objc private func handleTimer(_ timer: Timer) {
        // ② Timer の間隔秒を総秒数に加算する
        timerTotalDuration += timer.timeInterval
        print("timer fired. total: \(timerTotalDuration)")
    }
}

 

各箇所を詳しく説明

① Timer のスケジューリングと保持

timer = Timer.scheduledTimer(
    timeInterval: 5,
    target: self,
    selector: #selector(handleTimer(_:)),
    userInfo: nil,
    repeats: true
)

scheduledTi関数 は下記の引数を指定して、実行します。

引数名 指定する要素
timeInterval タイマーが発火する秒数を指定
target selector に支持する関数を持っているインスタンス指定
selector 実行する関数を指定
userInfo 関数に引き渡したいDictionaryを指定
repeats 繰り返しをするか否かを指定

実行すると Timer が生成されて、自動的にスケジューリングされ動き始めます
timeInterval で指定した時間がすぎると、selector で指定した関数が実行されます。
scheduledTimer関数 は実行されると生成した Timerを返却するので、上記の例だと var timerTimer が格納されます。
 

② Timer の間隔秒を総秒数に加算する

@objc private func handleTimer(_ timer: Timer) {
    timerTotalDuration += timer.timeInterval
    print("timer fired. total: \(timerTotalDuration)")
}

今回は加算型のタイマーを作るのが目的なので、クラスが持つ var timerTotalDuration に経過した時間を加算していきます。
timeInterval5 を設定していたら5秒毎に handleTimer関数 が実行され、timer.timeInterval に格納されている 5timerTotalDuration に加算されていきます。
 

③ Timer のスケジューリングを破棄

timer?.invalidate()

今回はストップウォッチのように タイマーを一時停止 できるようにします。
ですが、 Timer のスケジュール自体をストップすることは出来ず、invalidate関数 で一度 Timer 自体を破棄しなければいけません。
再度タイマーを動かしたい時には scheduledTimer関数 をもう一度実行して、同じ設定の Timer を作り直す必要があります。

上記サンプルでは、startTimer()Timer が生成され、stopTimer() で破棄されます。
ですが経過した秒数は timerTotalDuration に保持してあるので、再開したい場合は startTimer() を実行し、新しい Timer で計測を再開します。
 

④ Timer のスケジューリングを破棄し、総秒数を0に戻す

stopTimer()
timerTotalDuration = 0

この箇所はいわゆる初期化になります。
stopTimer関数Timer を破棄(ストップ)し、加算していた総秒数を 0 に戻します。
これで、再度 startTimer関数 が実行された際には 0 からのカウントになります。
 

⑤ Timer のスケジューリング重複を回避

guard timer == nil else { return }

最後に⑤の箇所になりますが、この箇所は Timer を利用する際に気をつけたい部分 となります。

scheduledTimer関数 実行前に、var timerTimer が格納されていないかチェックしていますが、これは必ず行った方が良い判定です
というのも、scheduledTimer関数 を実行すれば Timer をいくらでも生成できてしまい、その都度新しい Timer が返却されてしまうからです。

例として下記のように実行した場合、参照を無くしたタイマーが発火し続ける事になります。

// 一つ目のタイマーを生成
var timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(handleTimer(_:)), userInfo: nil, repeats: true)

// 二つ目のタイマーを生成(timerに格納されていた一つ目のタイマー参照は、二つめのタイマー参照に上書きされる)
timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(handleTimer(_:)), userInfo: nil, repeats: true)

// 二つのタイマーが同時に走り続ける

// timer をRunLoopのスケジュールから破棄する(二つ目のタイマーが破棄される)
timer?.invalidate()

// timerが保持していたのは二つ目のタイマーだったので、一つ目は破棄されずに発火し続ける

なので、必ず破棄していない Timer への参照を失わないようにすべきです。

更に注意すべきなのは。scheduledTimer関数に指定する target によっては循環参照になるので、Timer を破棄しない限り、target に指定したインスタンスも解放されなくなります

// ViewControllerに var timer を持たせる
var timer: Timer?

// scheduledTimer関数の target に self を指定する(Timerがselfの参照をもつ)
timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(handleTimer(_:)), userInfo: nil, repeats: true)

// この時点で self は Timer の参照を持ち、Timer は self の参照を持つ( = 循環参照 )

// timer を削除する(二つ目のタイマーが破棄される)
timer?.invalidate()

// この時点で循環参照が解除される

つまり、Timer の参照はしっかり管理し、target に指定しているインスタンスが破棄されるべきタイミングで、必ず invalidate関数 を呼ばなければなりません。
そういったリスクを踏まえ、⑤の処理は必ず入れておく事をおすすめします。
 

合わせて注意したい事

【Swift】Timerがズレる (遅延する) 時の原因と対処法
【Swift】Timerの強参照が破棄できない時の対処法(deinit での破棄方法)
 

Info. ドキュメント

公式ドキュメント
 

Info. サンプルテスト環境

Mac OS: 10.13.6
Xcode: Version 10.0
 

7
8
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
8