I need to make a closure capturing self in init to initialize a property, but I can't use self because super.init hasn't been called. However, I can't call super.init because the property hasn't been initialized!
import Foundation
class Base {
}
class Sub: Base {
let timer: Timer
var value = 0
// option 1 - try to initialize the property
override init() {
// ERROR: 'self' used before 'super.init' call
timer = .scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
self!.value += 1
}
super.init()
}
// option 2 - try to initialize super first
override init() {
super.init() // ERROR: Property 'self.timer' not initialized at super.init call
timer = .scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
self!.value += 1
}
}
}
Is there any way out of this catch-22 other than making the property both optional and mutable, initialized as nil during super.init, then changing it after?
I kinda get why the compiler isn't ok with this situation, but it's annoying to have to make a property optional and mutable, when it should in fact neither be nil, nor mutated, once the class is fully initialized.
Worth noting that the compiler has no way of knowing that the object that's taking the closure capturing self isn't called immediately, and it's trying to save you from having the closure called with a half-initialized instance of self.
I took a crack at it. I think @benpious solution is cooler though
Edit: just realized I left at least two silly things in here
Sub(Sub()) // I think I left it over from playing around with initialization
self.init(Timer.self) // Think I was doing something here trying to see if I could make use of the metatype somehow
protocol MyProtocol {}
extension NSObject {
convenience init<MyProtocol>(_ :MyProtocol?=nil) {
self.init()
}
}
extension Timer: MyProtocol {}
class Sub: Timer {
static let shared = Sub()
required convenience init<T>(_: T?) where T : NSObject {
self.init(Timer.self)
Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: {[weak self] _ in
self?.value+=1
Sub.shared.value+=1
print(self?.value)
print(Sub.shared.value)
})
}
var value = 0
}
Here is my driver code
let course = Sub(Sub())
sleep(2)
print(course.value)
I put it in a func
@IBAction func loadScene(sender: UIButton)
Then when I click it, I see the singleton retaining the value as it is incremented
That won't work as when you include a variable in the capture list (doesn't matter weak or not) it is captured by value. To capture by reference (which you want in this case) don't include it in the capture list: