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