`'self' used before 'super.init' call` conflicts with `Property not initialized at super.init call`

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.

1 Like

Optional and mutable seems right to me in this case; supposedly you'd also cancel and set to nil the timer in some cases?

You could mark the property private or at least private(set) to not make it mutate from the outside.

I'd prefer simply:

deinit {
    timer.invalidate()
}

since there are no cases in which it would be valid for an instance of this object to exist, but the timer not to be running.

You need a local var inside the function:

init() {
    var futureSelf: Sub? = nil
    timer = .scheduledTimer(withTimeInterval: 1, repeats: true) { [weak futureSelf] _ in
            futureSelf?.value += 1
        }
    super.init()
    futureSelf = self
}

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.

4 Likes

@benpious the local var is a clever workaround, thanks! Just what I was looking for.

Exactly, that's why I feel okay about the error. I get that it's necessary, just was looking for a better way to avoid it. Thanks!

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

A lot of unnecessary here but it is fun to play around with the initialization process like this

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:

    override init() {
        weak var futureSelf: Sub?
        
        timer = .scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
            futureSelf?.value += 1
        }
        super.init()
        futureSelf = self
    }
3 Likes