Developers often need to do something many times and at regular intervals. One common application of such a use case is checking some process’s or device’s status, a technique called “polling.” In this tutorial, I’ll show you how to use the Swift 4.2 Timer
class in Xcode 10 to repeatedly perform a simple task, every second, for 1 minute (60 seconds) — and update the user interface (UI) on each “tick.” You’ll learn how to start a timer ticking, pause it, resume it, stop it, and do something on each tick. Here’s what we’ll create during this tutorial — and the source code is available for download from GitHub:
To emphasize that the UI remains responsive when a timer is used judiciously, I added a UISlider
to my storyboard and tested my code. Here’s the result:
If you want to read some background and history on timers, keep going from this point. If you want to jump right into coding timers, click here.
Timers are often used for the following purposes, which is why I mentioned polling:
- checking for new data;
- checking for changes made to data;
- checking for an existing device that woke up;
- checking for a new device that was connected to a Mac or iPhone; or,
- find out if a communications peer or server is still there, has crashed, has become disconnected, or has shut down.
Polling
Polling is a proven, predictable, and simple method for a client to regularly check for some changing condition(s) or state(s) on a server. Or, an app may need to poll a data source for updates. There are many other examples I could mention. Using the Timer
class for such purposes is often the best option to use in code. They’re semantically clear, easy to set up, and time-savers when development pressures are almost always high.
Yes, there are alternative solutions to polling like interrupts. There seems to be this never-ending debate over which technique is superior. These type of debates have merit in the sense that people definitely should always be thinking about improving software systems. On the other hand, such debates tend to bore me to near death as there rarely ever is a “perfect” solution and I’m a diehard pragmatist: I want techniques that work and are simple to implement.
Oftentimes, there is no alternative to polling. Suppose there’s some free public data source, like weather information, accessible through a REST API, where the data is encoded in JSON. Say the data owners require clients to sign up for an API key. Most such free services I’ve used just update data fields at the source — and that’s it. They don’t send push notifications to every developer with an API key. They expect clients to poll their API to find out if, for example, the temperature has changed. While some such services allow retail clients to subscribe to push notifications to get informed about changes, many don’t support this type of overhead for clients who are developers.
There are other use cases beyond polling, like coordinating an app’s performance of a series of steps at regular intervals.
Encoding a basic Timer
in Swift
I’ll walk you through setting up and configuring a Timer
in Swift, starting it ticking, pausing it, resuming it, stopping it, and restarting it. You can download my sample project from GitHub, which is finished and ready to run, and read along, or follow the steps I describe below and build the project from scratch. Either way, read along with me.
Create a new Xcode project using the iOS Single View App template.
Storyboard
Add two UIButton
instances, a UITextField
instance, and a UIProgressView
in Main.storyboard
. Add Auto Layout constraints. Create an IBAction
for each button and an IBOutlet
for the text field and progress view by control-dragging from the storyboard into ViewController.swift
, like this:
Open up the ViewController.swift
file. Add the following properties to the ViewController
class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
... // MARK: - Member properties // Declare an instance of the Timer class. var countingTimer: Timer? // Declare an Int to keep track of // each time Timer fires/ticks. var tickCount = 0 // Declare Double to specify seconds // between Timer ticks. let tickRate = 1.0 // Declare Int to specify total // timer ticks possible. let totalTicks = 60 // Declare Float to specify // progress bar increments. var progressIncrement: Float = 0.0 ... |
Remember that since we rarely initialize an instance of UIViewController
subclass programmatically, all those properties I just added were either optionals or had initial values assigned.
To keep my code “bounded,” I generally always set the maximum number of times that a timer can be fired (totalTicks
). Even in cases where my timers run for the whole life of an app, I generally enforce this boundary condition to help during development — and especially debugging — of timer code.
Initialization
When we start the app, we’ll do some simple initialization:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
... // MARK: - UIViewControllerDelegate override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. // The first tick is always zero ("0"). timerTickTextField.text = "0" // No progress yet. progressView.progress = 0.0 // Calculate the size of progress // bar increments (going from 0.0 to 1.0). progressIncrement = 1.0 / Float(totalTicks) } ... |
Creating and starting the timer
To start a timer, we literally need to create one. In use cases like I laid out in the introduction to this tutorial we use the Timer
class’s scheduledTimer
type method. Please review the method’s documentation. It says that the scheduledTimer
method “Creates a timer and schedules it on the current run loop in the default mode.” I want you to learn about the Timer
class’s lifecycle, so we’ll create our Timer
and destroy Timer
instances to allow starting, pausing, and resuming. Add the following code to the “Start” button’s IBAction
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
... // MARK: - User interactions @IBAction func startButtonTapped(_ sender: Any) { // Create and configure the timer for 1.0 second ticks. countingTimer = Timer.scheduledTimer(timeInterval: tickRate, target: self, selector: #selector(onTimerTick), userInfo: "Tick: ", repeats: true) // Make the timer efficient. countingTimer?.tolerance = 0.15 // Helps UI stay responsive even with timer. RunLoop.current.add(countingTimer!, forMode: RunLoop.Mode.common) } ... |
The first line in this function creates a Timer
instance I’ve declared as countingTimer
. Let’s review the scheduledTimer
method’s arguments:
timeInterval
: The time between “firings of the timer” (kinda like the time between ticks on a clock, but less than or equal to or more than a second);target
: The object on which we call theselector
, and since we define the selector inViewController
, that’s this object,ViewController
, or as we write it as an argument,self
;selector
: A function that is called on each timer fire or “tick” as I like to call them;userInfo
: Almost any extra info that we want to associate with our timer — and send to theselector
;repeats
: set this totrue
to make your timer repeatedly call theselector
, once pertimeInterval
— we’ll discussfalse
in the conclusion.
Wait a minute. Notice that the line calling scheduledTimer
now has the error (!) Use of unresolved identifier ‘onTimerTick’. That’s because the selector
argument of scheduledTimer
points to a function named onTimerTick
that gets called on every timeInterval
as specified by my tickRate
constant — but onTimerTick
hasn’t been defined yet! Let’s do that next, and note that we’ll discuss tolerance
and RunLoop
in the conclusion.
What to do on each tick
You can name your timer fire handler function whatever you want, but Apple advises you use the following declaration:
1 |
- (void)timerFireMethod:(NSTimer *)timer |
Hey Apple, that’s Objective-C and I’ve got the “Language” setting on the “Documentation” site set to “Swift.” Oops! Oh, well… Y’all know how to translate that to Swift, like I did here:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
... // MARK: - Timer tick handler; called every time timer fires. @objc func onTimerTick(timer: Timer) -> Void { // Get custom data sent from timer. let preface = timer.userInfo as? String // Move the progress bar along one increment, // reflecting another timer tick. progressView.progress += progressIncrement // BOUNDARY: I only let the timer // tick for 60 seconds. if tickCount >= totalTicks { countingTimer?.invalidate() // Destroy timer. timerTickTextField.text = "DONE" } // We haven't hit the boundary, so count // the current tick and display. else { tickCount += 1 timerTickTextField.text = preface! + String(tickCount) } } // end func onTimerTick ... |
What? Another error? This time it’s (!)Argument of ‘#selector’ refers to instance method ‘onTimerTick(timer:)’ that is not exposed to Objective-C. Why?
While it seems like Swift has its own native Timer
, it’s really calling Objective-C. How do I know? I looked at Apple’s documentation on Timer
. Here’s the class’s declaration:
1 |
class Timer : NSObject |
Every time our (Objective-C) timer fires, it calls our onTimerTick
Swift function, and therefore (see bullet “SE-0160”):
…the compiler will emit warnings about places where the Objective-C entry points for these inference cases are used, e.g., in a #selector
or #keyPath
expression, via messaging through AnyObject
, or direct uses in Objective-C code within a mixed project. The warnings can be silenced by adding an explicit @objc
.
So I let the Xcode error helper fix the problem:
Pausing/stopping the timer
When you want to start or “resume” a Timer
, you must create and configure one as I showed you above. When you want to stop or “pause” a Timer
, you must destroy (invalidate()
) it as I showed you above and will show you in the next code snippet.
If you read the documentation on Timer
, you should’ve noted that:
Once scheduled on a run loop, the timer fires at the specified interval until it is invalidated. A nonrepeating timer invalidates itself immediately after it fires. However, for a repeating timer, you must invalidate the timer object yourself by calling its invalidate()
method. … Invalidating the timer immediately disables it so that it no longer affects the run loop. … Once invalidated, timer objects cannot be reused.
Let’s finish up by adding code to be called when our “Stop” button is pressed, allowing us to pause or stop our timer:
1 2 3 4 5 6 7 8 |
... @IBAction func stopButtonTapped(_ sender: Any) { // Destroy timer. countingTimer?.invalidate() } ... |
Hey. That was pretty simple — overall. There were some error messages we had to deal with. And there were a couple of important sounding properties we set, but which I haven’t yet explained. Before concluding by defining these timer details, let’s look at an example of a timer.
Applications of timers
Let me show you how I have used timers to simplify the coding of features that may seem complex and where you would probably assume that I would have to use a different technology to implement.
Here’s an app that I’m working on that animates the “filling” of a UIViewController
subclass’s UIView
color by placing “tiles” on the view. I start in the upper, left-hand side of the view controller’s view and add tiles in a column by column order, where each column is filled from top to bottom. You’ll understand when you watch this video:
When I’m done tiling, this is what you see:
You’ll see an extra bit of padding on the right side. That’s because, while my algorithm for calculating the sizes of tiles and the order in which they’re filled is innovative, I haven’t yet had the time to make it look perfect.
I used the Xcode Instruments’ Time Profiler template to confirm the fact that my tiling code is indeed lightweight:
As you can see, the UI and app in general remain responsive while tiling. In fact each tile animation very briefly uses approximately just 20% of CPU on the main thread.
Conclusion
I’ve made a convincing case as to the extensive utility of timers in app coding, so I don’t need to rehash this tutorial. While describing my code, I did show you some code that I didn’t fully explain. I wanted you to concentrate on timer essentials. Now that you understand the Swift Timer
class, let’s take the time to cover a few details that are worth mentioning.
When we create a Timer
class instance using the scheduledTimer
type method and a repeats
argument value of false
:
You specify whether a timer is repeating or nonrepeating at creation time. A nonrepeating [false
] timer fires once and then invalidates itself automatically, thereby preventing the timer from firing again.
If you’ve ever wondered how to start some task at some predetermined time in the future — like after your app starts — this is one way of doing it.
You should have noticed that, when creating my timer instances, I set the tolerance
and RunLoop
. I set tolerance
to 0.15 because:
Setting a tolerance for a timer allows it to fire later than the scheduled fire date. Allowing the system flexibility in when a timer fires increases the ability of the system to optimize for increased power savings and responsiveness.
… and:
A general rule, set the tolerance to at least 10% of the interval, for a repeating timer. Even a small amount of tolerance has significant positive impact on the power usage of your application.
I used RunLoop.Mode.common
when scheduling my timers in a run loop. I wish there was a simple explanation for this in Apple’s documentation, but I can’t find any. The topic is discussed at links like this one and this one, but I can’t find anything clear and concise.
So trust my experience and research into putting all the pieces together that RunLoop.Mode.common
makes your timer more responsive in terms of the UI. In other words, scrolling or tapping on the screen does not interfere with your timer’s timing.
We’ve covered a whole lot. I hope you’re feeling more confident about using the Swift Timer
class. I’ve used them to great success in my apps. You will too.