Ultimate guide on Timer in Swift
Jul 31, 2024A timer is a class used to perform any task after a specific time interval. Timers are super handy in Swift or scheduling tasks with a delay or scheduling repeating work.
Table of Contents
- Why do we need timers in Swift
- Repeating vs Non-repeating Timers
- How to schedule a repeating task
- How to add context to a Timer
- How to schedule a repeating task using a closure
- How to stop a repeating timer
- How to schedule a non-repeating task
- Timer Tolerance & RunLoop Modes
Why do we need timers in Swift
You would often face requirements where you need to perform a particular task repeatedly within a particular time interval, or scenarios where you need to perform certain actions after a defined time. In such cases, it is ideal to leverage Timers in your codebase.
Timer can be used in such common scenarios:
- Say, you have to automatically call a particular method every 5 seconds.
- If you want to send a local notification once you terminate the app.
- Sync your cart details with the backend by sending updated details after each X time.
Repeating vs Non-repeating Timers
You have to specify whether a timer is repeating or non-repeating at the time of creation. Here is the main difference between both:
-
A non-repeating timer fires once and then invalidates itself automatically, so it prevents the timer from firing again.
-
A repeating timer fires and then reschedules itself on the same run loop. A repeating timer always schedules itself based on the scheduled firing time, as opposed to the actual firing time.
For example, if a timer is scheduled to fire at a specific time and every 10 seconds after that, the scheduled firing time will always fall on the original 10-second time intervals, even if the actual firing time gets delayed.
How to schedule a repeating task
Let's see how to create a repeating task.
override func viewDidLoad() {
super.viewDidLoad()
// scheduling a timer in repeat mode after each 2 seconds.
let timer = Timer.scheduledTimer(timeInterval: 2,
target: self,
selector: #selector(handleTimerExecution),
userInfo: nil,
repeats: true)
}
@objc private func handleTimerExecution() {
print("timer executed...")
}
In the above code,
-
A timer is created with the class method
scheduledTimer
with parameters like time interval, userInfo and repeat mode, etc. Remember, time intervals always take time in seconds. -
Here, we've set the `timeInterval' to 2 seconds,meaning the action (handleTimerExecution) will execute after every 2 seconds.
-
With
userInfo
you can pass additional information along with the Timer object. The object should be in dictionary format. -
We have used the
target-action
mechanism to perform the task. It is a mechanism to call a specific method on an object. -
To make the timer repeatable, the
repeat
parameter is set to true.
When you run the above code, you will see that the handleTimerExecution
method will continue to be called indefinitely as we haven't specified any condition when the timer should end. Even though the timer constant was created inside the viewDidLoad()
local context, it will outlive that function, since Runloop keeps a reference to the timer object.
Note: The @objc attribute makes the handleTimerExecution()
function available in Objective-C. And the timer class is a part of Objective-C runtime, that's why we need the @objc attribute here.
How to add context to a Timer
You can also pass additional information to the timer object during it's creation.
private var timer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
let userInfo = ["fullName": "John Martin", "age": 30] as [String : Any]
self.timer = Timer.scheduledTimer(timeInterval: 1,
target: self,
selector: #selector(handleTimerExecution(_:)),
userInfo: userInfo,
repeats: true)
}
@objc private func handleTimerExecution(_ timer: Timer) {
if let userInfo = timer.userInfo as? [String: Any] {
print("user info: \(userInfo)")
self.timer?.invalidate()
}
// here is a fallback to end timer if userInfo not found
}
How to schedule a repeating task using a closure
There are multiple ways via which you can scedule a repeating task and one of them is with closures.
let timer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true) { timer in
print("timer executed...")
}
In the approach used above, you will receive the timer's execution within the same block. You don't need to define a separate method like a target-action mechanism.
Did you find this cool???? yesss, closure based timer is really cool.
How to stop a repeating timer?
To stop a repeated timer in Swift, you simply have to call the invalidate()
method provided by the timer class.
private var timerCurrentCount = 0
let timer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true) { timer in
// check for max execution count of timer.
if self.timerCurrentCount == 5 {
timer.invalidate() // invalidate the timer
} else {
print("timer executed...")
self.timerCurrentCount += 1
}
}
Points to note here:
-
Be cautious with the timer's termination, as a repeating timer will continue indefinitely if the stop conditions are not met.
-
For the target-action mechanism, you have to make the timer variable as an instance variable and define it outside the function.
private var timer: Timer? override func viewDidLoad() { super.viewDidLoad() self.timer = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(handleTimerExecution), userInfo: nil, repeats: true) } @objc private func handleTimerExecution() { if self.timerCurrentCount == 5 { self.timer?.invalidate() // invalidating timer } else { print("timer executed...") self.timerCurrentCount += 1 } }
How to schedule a non-repeating task
Using a timer, creating a non-repeating task is very similar to how you create a repeated task. You simply have to set the repeat
parameter to false
. This will ensure that the timer will fire only once.
override func viewDidLoad() {
super.viewDidLoad()
let timer = Timer.scheduledTimer(timeInterval: 1,
target: self,
selector: #selector(handleTimerExecution(_:)),
userInfo: userInfo,
repeats: false)
}
In Swift, we also have an alternate approach to define a non-repeating task using GCD.
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
print("timer executed")
}
In the above code, the scheduled task will execute after 5 seconds from the current time, where now()
function returns the current time. In this approach, you don't need to invalidate the timer, RunLoop automatically invalidates it.
Timer Tolerance & RunLoop Modes
When you schedule a timer, it's managed by a RunLoop. The run-loop keeps looping and execute tasks for repeating timers. When no tasks are left to execute, the run-loop waits or quits.
It's important to note that an increasing number of timers in the app increases the risk of less responsiveness and more power usage.
To reduce the power consumption of the app, you can use the tolerance property while creating the timer. It's default value is set to 0.0 but can be modified and can be given a value between 0 and 1.
For example, you want to create a repeating timer with the tolerance
value of 0.1. This means the system will fire the timer any time between the scheduled fire date and the scheduled fire date plus tolerance time (i.e. 0.1).
timer?.tolerance = 0.1
How to utilize RunLoop?
RunLoop mode is a collection of input sources like touch, click, swipe, drag, timer events etc.
Each timer fires on the main thread
and is attached to a RunLoop
. Since the main thread is also responsible for UI drawing, user interaction handling etc, the UIs might be unresponsive if say the main thread is busy in timer execution.
How to solve this problem?
here are three types of RunLoop modes that you can use to handle such problems:
-
default: It handles input sources that are not
NSConnectionObjects
. -
common: It handles a set of other run loop modes like timers, observers etc.
-
tracking: It handles the UI responsiveness in the app.
Therefore, common mode is good to use when we have multiple repeating timers in the app to avoid such problems.
RunLoop.current.add(timer, forMode: .common)
Note: Remember, this is not needed for usual implementation. This line will only be required in special cases discussed above.
Points to remember
-
When your app enters the background, iOS pauses any running timers and when the app enters the foreground again, iOS resumes the timers.
-
Each timer fires on the main
thread
and is attached to aRunLoop
. -
Don't forget to stop the timer once the task is completed to reduce battery drain.
-
When you use a timer, you can't be sure that it will be executed at the same time. But you can only be sure that you will get a callback according to timeInterval sets on the timer.
Where to go next?
In this article, you learned about how Timers
work, how it works with RunLoop
and how you can improve app performance with multiple timers. Now, you can do some practice by making small apps like StopWatch, Clock, CountDown etc. We encourage you to read more related articles like Slider in SwiftUI, List in SwiftUI, Geometry Reader in SwiftUI.