17

I was creating my own custom tableViewCell and then I got an error saying:

'required' initializer 'init(coder:)' must be provided by subclass of 'UITableViewCell'

I looked it up and obviously it's a must to implement that as well. But this led to my confusion about required vs. designated initializers

Apple Docs says:

Required Initializers:

Write the required modifier before the definition of a class initializer to indicate that every subclass of the class must implement that initializer:

Designated initializers

Designated initializers are the primary initializers for a class. A designated initializer fully initializes all properties introduced by that class and calls an appropriate superclass initializer to continue the initialization process up the superclass chain.

Are the following statements correct:

  • A required initializer is always a designated initializer
  • Every designated initializer is not necessarily a required initializer
  • A class can only have one required initializer, however it can have multiple designated initializers?

Having that said I still don't fully understand their functional differences.

Community
  • 1
  • 1
mfaani
  • 33,269
  • 19
  • 164
  • 293
  • 1
    I believe you can have a required convenience initializer, so the first statement isn't correct. – dan Jan 11 '17 at 17:21

1 Answers1

22

Required initialisers and designated initialisers are not really related, though the associated keywords required and convenience are both used to specify restrictions on subclasses.

Required Initialisers

A required initialiser makes a guarantee that you can initialise a type, or any of its sub-types, with that initialiser. If you have an initialiser in a protocol and you conform something to that protocol, you have to use required (if it's a class) because that protocol guarantees that the initialiser is present on that class, and any of its subclasses. When you use required on an initialiser of a class, that signals that all of its subclasses can also be initialised using that method. This means you also need to add that initialiser to any of its subclasses.

protocol TestProtocol {
    init()
}

class TestClass: TestProtocol {
    required init() {

    }
}

Here, the required keyword must be present because any subclasses of TestClass must also provide init() (because they also conform to TestProtocol).

Having a required initialiser allows you to initialise a class without knowing what it is at compile time, which is useful for a variety of reasons:

let classType: TestProtocol.Type = TestClass.self
let object = classType.init()

If your class conformed to multiple protocols, each with a different initialiser for example, each of those initialisers must also be required:

protocol OtherProtocol {
    init(thing: Int)
}

class OtherClass: TestClass, OtherProtocol {
    let thing: Int

    required init() { // Required from superclass/its protocol
        self.thing = 0
    }

    required init(thing: Int) { // Required from new protocol
        self.thing = thing
    }
}

Note that adding super.init() isn't required in this special case, because Swift will automatically include the call if it takes no parameters.

In all the above examples, the initialisers are designated because they do not include the convenience keyword.

Even if you didn't have any protocols, you can still make use of required by initialising a type of a class which isn't known at compile time:

class BaseClass {
    let value: Int

    required init(value: Int) {
        self.value = value
    }
}

class SubClass: BaseClass {
    required init(value: Int) { // Required from superclass
        super.init(value: value) // Must call desginated initialiser of superclass
    }
}

let someBaseClassType: BaseClass.Type = SubClass.self
let someBaseClassInstance = someBaseClassType.init(value: 1)

Designated Initialisers

A designated initialiser is one which isn't a convenience initialiser (i.e, marked with convenience). A designated initialiser must make sure that all properties of the class have a value before the initialiser finishes (or a super initialiser is called). Convenience initialisers only don't have this requirement because they must themselves call a designated initialiser.

class OtherSubClass: BaseClass {
    convenience required init(value: Int) {
        self.init() // Must call designated initialiser of this class
    }

    init() {
        super.init(value: 0) // Must call designated initialiser of superclass
    }
}

(This is fairly contrived example.)

In my experience, convenience initialisers are rarely useful and I tend to find the problems they solve can be solved using optional arguments on designated initialisers instead. One also needs to consider the fact that initialisers can't call convenience initialisers on their superclass, so make sure you don't have any convenience initialisers which provide functionality that your designated initialisers don't if you intend your class to be subclassed!


Structs and enums don't use the required or convenience keywords because these keywords are both used to indicate initialisation rules for subclasses, which only classes support: The required keyword indicates that subclasses must provide that initialiser, and the convenience keyword indicates that subclasses cannot call that initialiser. Despite not having the keywords, they must still provide initialisers defined in any protocols they conform to, and you can write 'convenient' initialisers which call self.init, just without the convenience keyword.


To respond to your statements:

  • Required initialisers don't have to be designated.
  • Designated initialisers don't have to be required.
  • Classes can have multiple required and designated initialisers.
Robert
  • 5,735
  • 3
  • 40
  • 53
  • Thanks for code-snippet, it makes it a lot more easier to understand. Can you also add a simple example for multiple required initializers? I'm having a hard time making sense of them – mfaani Jan 11 '17 at 17:34
  • Wow. I come from "top-down" land and learned OOP on my own 20 years ago. Still learning protocol programming. This blows my mind. Up-voted. –  Jan 11 '17 at 17:36
  • @Honey: Done, the code snippets all follow on from each other. – Robert Jan 11 '17 at 17:38
  • So in a OOP world it wouldn't make sense to have multiple required inits, however in a POP world it does make sense? – mfaani Jan 11 '17 at 17:41
  • 1
    @Honey There is a use for `required` even without protocols (and having multiple required), as you can use the *type* of the class to initialise an instance too (rather than the type of a protocol); I'll add an example... – Robert Jan 11 '17 at 17:41
  • 1
    Also note the following corner case where you have a `required convenience` initializer in your base class (e.g. use to fulfil the blueprints of some protocol the base class conforms to). Generally `convenience` initializers for some class is not inherited by derived classes, so derived classes would have to implement their own version of this `required` initializer (not necessarily a `convenience` one). However, if a subclass implements all of the base class's _designated_ initializers, it will inherit all the `convenience` initializers of its superclass, and hence also the `required` one. – dfrib Jan 11 '17 at 17:54
  • 1
    ... which means the subclass will conform to e.g. the protocol in which that initializer was blueprinted (to which the superclass conforms) by having access to the (`required`) `convenience` initializer of its superclass. (Another less interesting corner case (since its more "expected") is when the subclass does not define any designated initializers of its own, in which case it will inherit all of the designated initializers of its superclass). – dfrib Jan 11 '17 at 17:55
  • There is a good reason for initialisers not to call super convenience initialisers: this guarantee the init chain will be fully correct and end up with a designated initialiser of the top level class. I added an answer to https://stackoverflow.com/questions/24122421/why-cant-swift-initializers-call-convenience-initializers-on-their-superclass – Julien Jan 11 '17 at 18:22
  • 1
    @Julien That answer kind of makes sense (and I'd be interested if it was the official reason that it's not allowed), though I'm not convinced they couldn't have made a work around, such as calling `self.init` in a convenience init only call initialisers in the same class rather than the subclass! I'll remove my 'for no reason' though as it was a bit hasty... – Robert Jan 11 '17 at 18:38
  • This is the official reason and is inherited from Objective C. Trying to understand the call graph is (generally) impossible to do at compile time and your proposed idea would break when you are just overriding designated initialisers (which automatically inherits convenient ones). In the end, the current rule is the only "non surprising" + bug free one. – Julien Jan 12 '17 at 15:38
  • Re: *[structs] also can't have convenience initialisers, though I'm not certain why*... convenience initializers are specific to classes because they're specific to inheritance. See [Initializer Delegation for Class Types](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-ID219) in *The Swift Programming Language*. – rickster Jan 12 '17 at 22:29
  • @rickster Thanks! I've modified my answer. – Robert Jan 13 '17 at 03:58
  • A very informative case is when your class is `final` and you know you won't subclass: “Alternatively, if you know that you **won't subclass** the Note object, you could remove the `required` keyword from the initializer and mark the class as `final`—meaning that you can't define a subclass of Note. You would do so by adding the `final` keyword before the class definition” (I didn't change the excerpt. The Note class could be **any** kind of a class) From:[App Development with Swift.” Apple Inc. - Education, 2017](https://itunes.apple.com/us/book/app-development-with-swift/id1219117996?mt=11) – mfaani Aug 24 '17 at 00:01