Combine and Timers
In this short reading we go through RunLoops and Timer class in Combine Framework.
Table of contents
- RunLoop
- Timer
- Using DispatchQueue
RunLoop
In short
A
RunLoop
object processes input for sources, such as mouse and keyboard events from the window system andPort
objects. ARunLoop
object also processesTimer
events.
RunLoop class conforms to Scheduler protocol that let us to use timers.
RunLoop class is not thread safe and we should only call RunLoop methods for the run loop of the current thread.
let runLoop = RunLoop.main
let subscription = runLoop.schedule(
after: runLoop.now,
interval: .seconds(2),
tolerance: .milliseconds(100)
){
print("Timer fired")
}
Timer does not pass any value and does not create a publisher. It starts at the date specified in the “after” argument with the specified interval and tolerance. The Cancellable RunLoop returns lets us stop the timer after a while.
An example of stopping the timer:
runLoop.schedule(after: .init(Date(timeIntervalSinceNow: 5.0)))
{
subscription.cancel()
}
Timer
The old familiar Timer class has being exposed to the Combine framework by a new API.
let publisher = Timer.publish(every: 1.0, on: .main, in: .common)
let publisher2 = Timer.publish(every: 1.0, on: .current, in: .common)
“on” argument is for the RunLoop that we attach the timer to.
“in” argument is for what runLoop mode we are running the timer on.
Note that the “Timer.publish(every: 1.0, on: .current, in: .common)” should be run on on DispatchQueue.main otherwise it may cause unpredictable results.
Timer publisher is of kind connectable publishers. It means it won’t emit values until we connect it or use autoconnect operator to connect right away.
let publisher = Timer
.publish(every: 1.0, on: .main, in: .common)
.autoconnect()
Using DispatchQueue
In this approach we use a little hack and combine using the PassThroughSubject publisher and DispatchQueue.
let queue = DispatchQueue.main
let timerPublisher = PassthroughSubject<Int, Never>()
var timerCounter = 0
let cancellable = queue.schedule( after: queue.now,
interval: .seconds(1) ){
timerPublisher.send(timerCounter)
timerCounter += 1 }
let subscription = source.sink {
print("Timer Value is \($0)")
}