96

Consider the two classes:

class A {
    var x: Int

    init(x: Int) {
        self.x = x
    }

    convenience init() {
        self.init(x: 0)
    }
}

class B: A {
    init() {
        super.init() // Error: Must call a designated initializer of the superclass 'A'
    }
}

I don't see why this isn't allowed. Ultimately, each class's designated initializer is called with any values they need, so why do I need to repeat myself in B's init by specifying a default value for x again, when the convenience init in A will do just fine?

Robert
  • 5,735
  • 3
  • 40
  • 53
  • 2
    I searched for an answer but I can't find any that would satisfy me. It's probably some implementation reason. Maybe searching for designated initializers in another class is much easier than searching for convenience initializers... or something like that. – Sulthan Jun 09 '14 at 18:01
  • @Robert, thanks for your comments below. I think you could add them to your question, or even post an answer with what you received: "This is by design and any relevant bugs have been sorted out in this area.". So it looks they can't or don't want to explain the reason. – Ferran Maylinch May 17 '17 at 11:15

7 Answers7

28

This is Rule 1 of the "Initializer Chaining" rules as specified in the Swift Programming Guide, which reads:

Rule 1: Designated initializers must call a designated initializer from their immediate superclass.

https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html

Emphasis mine. Designated initializers cannot call convenience initializers.

There is a diagram that goes along with the rules to demonstrate what initializer "directions" are allowed:

Initializer Chaining

Cœur
  • 37,241
  • 25
  • 195
  • 267
Craig Otis
  • 31,257
  • 32
  • 136
  • 234
  • 88
    But why is it done this way? The documentation just says that it simplifies the design, but I don't see how this is the case if I have to keep repeating myself by continually specifying default values on designated initializers I mostly don't care about when the default ones in the convenience initializers will do? – Robert Jun 09 '14 at 14:43
  • See @Sulthan's answer. I was incorrect in stating that a designated initializer could call a convenience initializer in its own class - in fact, a designated initializer cannot call a convenience initializer at all. (Answer updated.) – Craig Otis Jun 09 '14 at 17:39
  • @Robert With regards to *why*, I still don't know. Maybe worth asking DTS or filing a radar? – Craig Otis Jul 18 '14 at 12:18
  • 6
    I filed a bug 17266917 a couple days after this question, asking to be able to call convenience initialisers. No response yet, but I've not had any for any of my other Swift bug reports either! – Robert Jul 18 '14 at 15:30
  • 8
    Hopefully they will allow to call convenience initialisers. For some classes in the SDK there is no other way to achieve a certain behaviour but calling the convenience initialiser. See `SCNGeometry`: you can add `SCNGeometryElement`s only using the convenience initialiser, therefore one can't inherit from it. – Pietro Saccardi Jul 27 '14 at 14:21
  • 4
    This is a very poor language design decision. From the very beginning of my new app I decided to develop with swift I need to call NSWindowController.init(windowNibName) from my subclass, and I just cannot do that :( – Uniqus Oct 31 '14 at 09:03
  • What if the superclass has no designated initializers? – sudo Mar 27 '15 at 01:28
  • @9000 In what situation would a class have no designated initializers? Give it a try in a playground. – Craig Otis Mar 27 '15 at 11:04
  • @CraigOtis Suppose you make `MyViewController` that subclasses `UIViewController`, and you don't write any initializers in it, then you make `TestViewController` that subclasses `MyViewController`. `MyViewController` has no designated initializers, so you can't make any initializers in `TestViewController` that call `super.init()`. I ran into this kind of problem and had to work around it by making `MyViewController` have some bogus initializer that calls `super.init(nibName: nil, bundle: nil)`, then I called that in `TestViewController`. – sudo Mar 28 '15 at 00:14
  • @sudo If you don't declare any initializers in a subclass, that subclass will automatically inherit all the initializers of its superclass. See https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-ID222 – Andy Hin Oct 09 '15 at 17:53
  • 1
    @AndyHin Yes, but the problem is the inherited initializers are not "designated" initializers, so subclasses of the subclass can't call them in their own initializers. I don't know whether this was fixed in newer versions of Swift; maybe it was because I haven't run into it in a long time. – sudo Oct 09 '15 at 19:33
  • 1
    @Robert: Did Apple respond by now? – Kaiserludi Dec 10 '15 at 12:10
  • 4
    @Kaiserludi: Nothing useful, it was closed with the following response: "This is by design and any relevant bugs have been sorted out in this area." – Robert Dec 10 '15 at 12:13
  • 1
    This is not an answer to the OP’s question. Should be a comment, not an answer. – Philippe-André Lorin Jan 02 '17 at 11:11
  • Apple has shot itself in the foot open sourcing the swift spec. Hence do not expect a meaningful answer from them. This is spinning out of control the way c++ did with the feature bloat and "unfortunate" design decisions. – Anton Tropashko Jul 25 '18 at 11:03
  • This doesn't answer the question – J. Doe Feb 17 '19 at 18:03
  • @Robert Ah. It seems like the "why" turns out to be "because convenience initializers work differently than expected". Specifically, according to beba's answer, convenience initializers don't call their own class's designated init, they call the _runtime_ class's designated init. Normally, I guess this means the convenience inits stick around through subclasses, arguably making them more convenient, but in the desired case, calling a convenience init from the designated init would result in the c-init calling right back to the d-init you started with, eventually yielding stack overflow. – Erhannis May 12 '20 at 17:53
26

Consider

class A
{
    var a: Int
    var b: Int

    init (a: Int, b: Int) {
        print("Entering A.init(a,b)")
        self.a = a; self.b = b
    }

    convenience init(a: Int) {
        print("Entering A.init(a)")
        self.init(a: a, b: 0)
    }

    convenience init() {
        print("Entering A.init()")
        self.init(a:0)
    }
}


class B : A
{
    var c: Int

    override init(a: Int, b: Int)
    {
        print("Entering B.init(a,b)")
        self.c = 0; super.init(a: a, b: b)
    }
}

var b = B()

Because all designated initializers of class A are overridden, class B will inherit all convenience initializers of A. So executing this will output

Entering A.init()
Entering A.init(a:)
Entering B.init(a:,b:)
Entering A.init(a:,b:)

Now, if the designated initializer B.init(a:b:) would be allowed to call the base class convenience initializer A.init(a:), this would result in a recursive call to B.init(a:,b:).

beba
  • 269
  • 3
  • 3
  • That's easy to work around. As soon as you call a superclass' convenience initializer from within a designated initializer, the superclass' convenience initializers would no longer be inherited. – fluidsonic Dec 03 '15 at 00:08
  • @fluidsonic but that would be insane because your structure and class methods would be changing depending on how they were used. Imagine the debugging fun! – kdazzle Jul 07 '16 at 13:06
  • 2
    @kdazzle Neither the class's structure nor its class methods would change. Why should they? -- The only problem I can think of is that convenience initializers must dynamically delegate to a subclass's designated initializer when inherited and statically to it's own class's designated initializer when not inherited but delegated to from a subclass. – fluidsonic Jul 07 '16 at 15:16
  • I think you can also get a similar recursion problem with normal methods. That depends on your decision when calling methods. For example, it would be silly if a language didn't allow recursive calls because you could get into an infinite loop. Programmers should understand what they are doing. :) – Ferran Maylinch May 17 '17 at 11:00
  • Interestingly, this isn't just an explanation of the safeguards convenience vs. designated initializers provide, it's a solution for compile errors like `Convenience initializer for 'MySubclass' must delegate (with 'self.init') rather than chaining to a superclass initializer (with 'super.init')`— the solution being to just override all of the superclass's designated initializers with basic implementations that pass everything to the corresponding `super.init(…)`. A small edit to the answer making that point clearer would be . – Slipp D. Thompson Dec 20 '22 at 05:10
16

It's because you can end up with an infinite recursion. Consider:

class SuperClass {
    init() {
    }

    convenience init(value: Int) {
        // calls init() of the current class
        // so init() for SubClass if the instance
        // is a SubClass
        self.init()
    }
}

class SubClass : SuperClass {
    override init() {
        super.init(value: 10)
    }
}

and look at:

let a = SubClass()

which will call SubClass.init() which will call SuperClass.init(value:) which will call SubClass.init().

The designated/convenience init rules are designed that a class initialisation will always be correct.

Julien
  • 3,427
  • 20
  • 22
  • 1
    While a subclass may not explicitly call convenience initializers from its superclass, it may _inherit_ them, given that the subclass supplies implementation of all of its superclass designated initializers (se [e.g. this example](https://gist.github.com/dfrib/26c7e54c3465861a632330f9eaf673dd)). Hence your example above is indeed true, but is a special case not explicitly related to a superclass's convenience initializer, but rather to the fact that a designated initializer _may not call a convenience one_, since this will yield to recursive scenarios such as the one above. – dfrib Jan 11 '17 at 18:52
1

I found a work around for this. It's not super pretty, but it solves the problem of not knowing a superclass's values or wanting to set default values.

All you have to do is create an instance of the superclass, using the convenience init, right in the init of the subclass. Then you call the designated init of the super using the instance you just created.

class A {
    var x: Int

    init(x: Int) {
        self.x = x
    }

    convenience init() {
        self.init(x: 0)
    }
}

class B: A {
    init() {
        // calls A's convenience init, gets instance of A with default x value
        let intermediate = A() 

        super.init(x: intermediate.x) 
    }
}
bigelerow
  • 533
  • 2
  • 14
1

Consider extracting the initialization code from your convenient init() to a new helper function foo(), call foo(...) to do the initialization in your sub-class.

SwiftsNamesake
  • 1,540
  • 2
  • 11
  • 25
evanchin
  • 2,028
  • 1
  • 22
  • 25
0

Look at the WWDC-video "403 intermediate Swift" at 18:30 for an in depth explanation of initializers and their inheritance. As I understood it, consider the following:

class Dragon {
    var legs: Int
    var isFlying: Bool

    init(legs: Int, isFlying: Bool) {
        self.legs = legs
        self.isFlying = isFlying
    }

    convenience initWyvern() { 
        self.init(legs: 2, isFlying: true)
    }
}

But now consider a Wyrm-subclass: A Wyrm is a Dragon with no legs and no wings. So the Initializer for a Wyvern (2 legs, 2 wings) is wrong for it! That error can be avoided if the convenience Wyvern-Initializer simply can't be called but only the full designated Initializer:

class Wyrm: Dragon {
    init() {
        super.init(legs: 0, isFlying: false)
    }
}
Ralf
  • 32
  • 2
  • 12
    That's not really a reason. What if I make a subclass when `initWyvern` makes sense to be called? – Sulthan Jun 09 '14 at 15:44
  • 4
    Yeah, I'm not convinced. There's nothing stopping `Wyrm` from overriding the number of legs after it calls a convenience initializer. – Robert Jun 09 '14 at 16:02
  • It is the reason given in the WWDC video (only with cars -> racecars and a boolean hasTurbo-property). If the subclass implements all designated initializers, than it too inherits the convenience initializers. I kinda see the sense in it, also I honestly had no much trouble with the way Objective-C worked. See also the new convention to call super.init last in the init, not first like in Objective-C. – Ralf Jun 09 '14 at 19:22
  • IMO the convention to call super.init "last" is not conceptually new, it is the same as that in objective-c, just that there, everything was being assigned a initial value(nil, 0, whatever) automatically. Its just that in Swift, we have to do this phase os initialisation ourselves. A benefit to this approach is we also have an option to assign a different initial value. – Roshan Jul 13 '14 at 11:08
-1

Why don't you just have two initializers - one with a default value?

class A {
  var x: Int

  init(x: Int) {
    self.x = x
  }

  init() {
    self.x = 0
  }
}

class B: A {
  override init() {
    super.init()

    // Do something else
  }
}

let s = B()
s.x // 0
Jason
  • 589
  • 6
  • 6