Diggory
(Diggory)
1
Hello,
A class must set all its properties to sensible values in its init() method, and you cannot refer to self in the init() method before all the properties are assigned sensible values.
So, how does one go about having a Timer as a property of a class that calls an instance method of that class when it fires?
e.g.
import Foundation
class RepeatingThing {
var aTimer: Timer
let timerDuration = 2.0
init() {
aTimer = Timer.scheduledTimer(withTimeInterval: timerDuration, repeats: true, block: {_ in self.timerDoSomething() } )
}
func timerDoSomething() {
print("The timer fired!")
}
}
let repeater = RepeatingThing()
Doesn't compile due to:
'self' captured by a closure before all members were initialized
I can't initialise the member without using a closure, but I can't use a closure because I haven't initialised the member.
glessard
(Guillaume Lessard)
2
Noting that you are declaring your aTimer property as a variable, I would point out that you can solve this example by changing that line thusly:
var aTimer: Timer? = nil
Personally, I have elected to use DispatchSourceTimer instead in a similar situation, as its scheduling is separate from its initialization.
Diggory
(Diggory)
3
OK, I think I've worked it out - it's not elegant, but it seems to work:
I have extracted the timer initialisation from out of init() and made it lazy.
lazy private var aTimer: Timer = {
var timer:Timer
if #available(OSX 10.12, *) {
print("Using closure timer.")
timer = Timer.scheduledTimer(withTimeInterval: timerDuration, repeats: true, block: {_ in self.timerDoSomething() } )
} else {
print("Using selector timer.")
timer = Timer(timeInterval: timerDuration, target: self, selector: #selector(timerDoSomething), userInfo: nil, repeats: true)
}
return timer
}()
1 Like
Diggory
(Diggory)
4
Thanks - I considered that but shied away as the idea of having to unwrap an optional every time I wanted to refer to the timer (which I could guarantee would be non-nil.) I suppose one could make a 'shadow' optional Timer and then have a computed timer property which force unwraps the shadow timer....
glessard
(Guillaume Lessard)
5
Another "possibility" is to assign a bogus Timer:
var aTimer = Timer(fire: .distantFuture, interval: 0, repeats: false, block: { _ in })
(and then invalidate before re-assigning, just to be safe.)
As for the DispatchSourceTimer version, you'd get
timer = DispatchSource.makeTimerSource(queue: .global(qos: .default))
timer.schedule(deadline: .now() + .seconds(1), repeating: .seconds(1))
timer.setEventHandler(handler: self.timerDoSomething)
timer.resume()
Every option seems dissatisfying in some way!
1 Like
xwu
(Xiaodi Wu)
6
This is precisely one of the use cases served by using an implicitly unwrapped optional: var aTimer: Timer!
eskimo
(Quinn “The Eskimo!”)
7
Personally, I have elected to use DispatchSourceTimer instead in a
similar situation, as its scheduling is separate from its
initialization.
Separating initialisation and scheduling isn’t the issue here, because Timer supports that just fine:
let t = Timer(fire: Date(), interval: 1.0, repeats: true, block: {
… your code here …
})
RunLoop.current.add(t, forMode: .default)
The difference is that DispatchSourceTimer lets your set the event handler separate from creating the source, whereas Timer does not.
Personally I’m not a fan of the design approach outlined in the first post here: If I create a class that’s active (runs a timer, hits the network, whatever), I never want it to start on initialisation. It is much better, in my experience, to separate initialisation from scheduling. And once you do that, you have to track whether the timer is running or not, at which point having an optional Timer is the right answer.
One reason I want the separation between initialisation and scheduling is that you need to separate descheduling from deinitialisation. When dealing with active classes, relying on the deinit to shut things down is a really bad idea.
Share and Enjoy
Quinn “The Eskimo!” @ DTS @ Apple
3 Likes
Diggory
(Diggory)
8
Thanks, that's very helpful.