Timer Accuracy in iOS

It’s buried in the docs, so you might not know how inaccurate Timer objects can be if you are trying to show the time. Let’s look at that inaccuracy and how to work around it.
You’ll find in the exercise file on GitHub a starter project where I’ve put together a demo app with three labels and a button. We’ll compare two ways of showing the time on a timer.

I set up a basic timer for you too. At top I declared a timer and a constant for the interval of the timer:

let interval = 0.0005
var timer = Timer()

In the action for the button I set up a timer.

@IBAction func didToggleTimer(_ sender: UIButton) {
        if !timer.isValid{
            timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true, block:
                { (timer) in

            })
        } else {
            timer.invalidate()
        }
    }

If you are not familiar with timers, when running, they are in a state called valid. If the timer is valid, I’ll invalidate it, shutting it off.
If the timer isn’t valid I’ll start a new one. There’s several ways of using timer objects. Since I like to start them immediately, I tend to use the scheduledTimer class method, adding the interval here, and making a repeating timer.
The timer has a closure on the end of it. This closure fires once every interval, having a parameter of a timer which is where we’ll do our work.
Above the timer, I’ll add the initialization for a count of the timer.

 var timerCount = 0.0

In the closure, I’ll use a simple way of telling the seconds elapsed: increment this count by the interval.

timerCount += self.interval

Then I’ll send that to the label

self.countLabel.text = String(format: " Timer %06.4f", timerCount)

Let’s compare this to another method: Checking the interval between a start date and the system clock date. I’ll make a constant for the startDate.

let startDate = Date()

I’ll use a DateInterval object to get a duration between that start date and now.

let duration = DateInterval(start: startDate, end: Date()).duration

And again I’ll send that to a label.

self.dateLabel.text = String(format: "Date %06.4f",duration)

Finally, I’ll show the difference between my two times in seconds, in a label

 self.differenceLabel.text = String(format: "Difference %06.4f", duration - timerCount)

I’ve set the interval to five thousandths of a second. Run this. You’ll see the difference between the date and timer are very different and diverging fast. While you’d expect with a smaller interval to get a more accurate time, the opposite is true. Timers have to wait for system resources. At bigger intervals, that is close to invisible. At smaller interval,s you can see the waiting delay creeping up. If you need accurate time, as in a stopwatch app, the best solution is to poll the clock and use a duration if you need the time, as we did here.
For most uses of a timer giving users the duration, you don’t need this resolution.

Change the interval to five hundredth of a second 0.05.

let interval = 0.05

The difference does not change as much, but is slightly increasing.

I’ll try one more interval 1/30 of a second, a standard speed for animation.

let interval = 1.0/30.0

Run again. Although it isn’t accurate time, for animation no one will know the difference. You only care about the interval itself not the accumulation of intervals.

For accurate timekeeping, poll the clock every interval. While on delays or other system types of timers, such as animation, you’ll find using incrementing timers works just fine.

The Whole Code

You can find the code for download on GitHub here.

//
//  ViewController.swift
//  TimerComparison
//
//  Created by Steven Lipton on 5/23/18.
//  Copyright © 2018 Steven Lipton. All rights reserved.
//
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var countLabel: UILabel!
    @IBOutlet weak var dateLabel: UILabel!
    @IBOutlet weak var differenceLabel: UILabel!
    let interval = 1.0/30.0
    var timer = Timer()

    @IBAction func didToggleTimer(_ sender: UIButton) {
       var timerCount = 0.0
        let startDate = Date()
        if !timer.isValid{
            timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true, block:
                { (timer) in
                    timerCount += self.interval
                    self.countLabel.text = String(format:"Timer %06.4f",timerCount)
                    let duration = DateInterval(start: startDate, end: Date()).duration
                    self.dateLabel.text = String(format:"Date %06.4f",duration)
                    self.differenceLabel.text = String(format:"Diffrence %06.4f",duration - timerCount)
            })
        } else {
            timer.invalidate()
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.