Class does not inherit initializer from superclass of superclass

I'm probably just not using Swift correctly, but I am curious why this does not work.

class A {
    var property: Int = 0
    init(foo: Int) {
        print(foo)
    }
}

class B: A {
    init(bar: Int) {
        super.init(foo: bar)
    }
}
class C: B {
    init() {
        super.init(foo: 0) // this gives a compiler error, saying the keyword should be `bar`
        self.property = 1 // this works completely fine
    }
}

Accessing properties of parent-parent classes seems to be fine, but accessing initializers doesn't.

I'm reading through the docs now to see if they can give me an answer.

EDIT: OK, so this explains it somewhat:
https://docs.swift.org/swift-book/documentation/the-swift-programming-language/initialization/#Automatic-Initializer-Inheritance

I may have to rework my API strategy.

If your subclass doesn’t define any designated initializers, it automatically inherits all of its superclass designated initializers.

https://docs.swift.org/swift-book/documentation/the-swift-programming-language/initialization/#Automatic-Initializer-Inheritance

As such, the simplest solution* to your situation is to not define a new designated initializer; there is no need for it.

class B: A {
  convenience init(bar: Int) {
    self.init(foo: bar)
  }
}

* The actual simplest solution is to never use inheritance for anything.

2 Likes

Understandable. Although, with what I'm working on, inheritance is pretty key. I think I figured out a solution though. Thanks for the note!

The reason is very simple. What will this program print (if it was compilable I mean)?

class A {
    var property: Int = 0
    init(foo: Int) { print(foo) }
}
class B: A {
    let b: String
    init(bar: Int) {
        b = "\(bar)"
        super.init(foo: bar)
    }
}
class C: B {
    init() { super.init(foo: 0) }
}
let c = C()
print(c.b)

Obviously it can't end well.

2 Likes

Moving to the "Using Swift" section as this doesn't have anything to do with development of the standard library.

1 Like

Your example is clear enough for what it's showing, but it doesn't demonstrate why the following doesn't allow for C.init(foo:) without making this into a convenience initializer.

class B: A {
  let b = ""

  init(bar: Int) {
    super.init(foo: bar)
  }
}

I.e. Why do you need to follow the same rules if all the new properties have defaults?

(Semantically, init(bar:) has to be a convenience initializer, so I don't have a problem with writing that with clarity, but the error message doesn't say anything about a potential contractual obligation that could be violated by using a designated initializer instead.)

Consider a slightly modified example:

class B: A {
    private var b: String = ""
    init(bar: Int) {
        b = "bar"
        super.init(foo: bar)
    }
    func checkBarness() {
        precondition(b == "bar")
    }
    func anyMethod() {
        checkBarness()
        ...
    }
}

If you somehow manage to construct C without calling B's initialiser you'd get "C" object that is effectively not a proper "B" as any of the B's methods will now fail at runtime. Swift wants to ensure that no matter what the subclass you are creating satisfies Liskov substitution principle ("objects of a superclass shall be replaceable with objects of its subclasses without breaking the application").


Another example where B doesn't add any variables of its own:

class A {
    private (set) var x = 0, y = 0
    init(x: Int, y: Int) { self.x = x; self.y = y }
}
class B: A {
    init(x: Int) {
        super.init(x: x, y: x + 1)
    }
    func checkProperB() {
        precondition(y == x + 1)
    }
    func anyMethod() { checkProperB(); ... }
}
class C: B {
    init() {
        super.init(x: 0, y: 0) // Imagine this was allowed
    }
}
let c = C()

Here B objects maintain invariant that y must be equal to x + 1. If it was possible to bypass B's initialiser when creating C you'd break the invariant and get improper B objects.


Yet another example:

class Worker {
    init(x: Int, y: Int) { ... }
    func doSomething() {}
}
class B: A {
    private var worker: Worker! // will crash if B's initialiser is bypassed
    // private var worker = Worker(x: 0, y: 0) // equally bad as worker is wrongly initialised
    
    init(x: Int) {
        super.init(x: x, y: x)
        worker = Worker(x: x, y: y)
    }
    func anyMethod() {
        worker.doSomething()
    }
}

Here you can't initialise the worker variable with the correct default value, as there is no x and y at that point, you need to do that in the B's init. You could initialise worker to an improper Worker(x: 0, y: 0) – and get improperly working B objects which is bad. Or leave var worker: Worker! uninitialised which will crash.

1 Like