146

Since Swift supports method and initializer overloading, you can put multiple init alongside each other and use whichever you deem convenient:

class Person {
    var name:String

    init(name: String) {
        self.name = name
    }

    init() {
        self.name = "John"
    }
}

So why would convenience keyword even exist? What makes the following substantially better?

class Person {
    var name:String

    init(name: String) {
        self.name = name
    }

    convenience init() {
        self.init(name: "John")
    }
}
Desmond Hume
  • 8,037
  • 14
  • 65
  • 112

6 Answers6

262

The existing answers only tell half of the convenience story. The other half of the story, the half that none of the existing answers cover, answers the question Desmond has posted in the comments:

Why would Swift force me to put convenience in front of my initializer just because I need to call self.init from it?`

I touched on it slightly in this answer, in which I cover several of Swift's initializer rules in details, but the main focus there was on the required word. But that answer was still addressing something that's relevant to this question and this answer. We have to understand how Swift initializer inheritance works.

Because Swift does not allow for uninitialized variables, you are not guaranteed to inherit all (or any) initializers from the class you inherit from. If we subclass and add any uninitialized instance variables to our subclass, we have stopped inheriting initializers. And until we add initializers of our own, the compiler will yell at us.

To be clear, an uninitialized instance variable is any instance variable which isn't given a default value (keeping in mind that optionals and implicitly unwrapped optionals automatically assume a default value of nil).

So in this case:

class Foo {
    var a: Int
}

a is an uninitialized instance variable. This will not compile unless we give a a default value:

class Foo {
    var a: Int = 0
}

or initialize a in an initializer method:

class Foo {
    var a: Int

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

Now, let's see what happens if we subclass Foo, shall we?

class Bar: Foo {
    var b: Int

    init(a: Int, b: Int) {
        self.b = b
        super.init(a: a)
    }
}

Right? We added a variable, and we added an initializer to set a value to b so it'll compile. Depending on what language you're coming from, you might expect that Bar has inherited Foo's initializer, init(a: Int). But it doesn't. And how could it? How does Foo's init(a: Int) know how to assign a value to the b variable that Bar added? It doesn't. So we can't initialize a Bar instance with an initializer that can't initialize all of our values.

What does any of this have to do with convenience?

Well, let's look at the rules on initializer inheritance:

Rule 1

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

Rule 2

If your subclass provides an implementation of all of its superclass designated initializers—either by inheriting them as per rule 1, or by providing a custom implementation as part of its definition—then it automatically inherits all of the superclass convenience initializers.

Notice Rule 2, which mentions convenience initializers.

So what the convenience keyword does do is indicate to us which initializers can be inherited by subclasses that add instance variables without default values.

Let's take this example Base class:

class Base {
    let a: Int
    let b: Int

    init(a: Int, b: Int) {
        self.a = a
        self.b = b
    }

    convenience init() {
        self.init(a: 0, b: 0)
    }

    convenience init(a: Int) {
        self.init(a: a, b: 0)
    }

    convenience init(b: Int) {
        self.init(a: 0, b: b)
    }
}

Notice we have three convenience initializers here. That means we have three initializers that can be inherited. And we have one designated initializer (a designated initializer is simply any initializer that's not a convenience initializer).

We can instantiate instances of the base class in four different ways:

enter image description here

So, let's create a subclass.

class NonInheritor: Base {
    let c: Int

    init(a: Int, b: Int, c: Int) {
        self.c = c
        super.init(a: a, b: b)
    }
}

We're inheriting from Base. We added our own instance variable and we didn't give it a default value, so we must add our own initializers. We added one, init(a: Int, b: Int, c: Int), but it doesn't match the signature of the Base class's designated initializer: init(a: Int, b: Int). That means, we're not inheriting any initializers from Base:

enter image description here

So, what would happen if we inherited from Base, but we went ahead and implemented an initializer that matched the designated initializer from Base?

class Inheritor: Base {
    let c: Int

    init(a: Int, b: Int, c: Int) {
        self.c = c
        super.init(a: a, b: b)
    }

    convenience override init(a: Int, b: Int) {
        self.init(a: a, b: b, c: 0)
    }
}

Now, in addition to the two initializers we implemented directly in this class, because we implemented an initializer matching Base class's designated initializer, we get to inherit all of Base class's convenience initializers:

enter image description here

The fact that the initializer with the matching signature is marked as convenience makes no difference here. It only means that Inheritor has just one designated initializer. So if we inherit from Inheritor, we'd just have to implement that one designated initializer, and then we'd inherit Inheritor's convenience initializer, which in turn means we've implemented all of Base's designated initializers and can inherit its convenience initializers.

Community
  • 1
  • 1
nhgrif
  • 61,578
  • 25
  • 134
  • 173
  • @nhgrif "How does Foo's init(a: Int) know how to assign a value to the b variable that Bar added? It doesn't. So we can't initialize a Bar instance with an initializer that can't initialize all of our values." Hi, nhgrif, I qoute your answer, I don't understand why Swift can't just inherit the init(a: Int) and add a int(b: int) in Subclass. excuse for the naive question. But I really want to know. – SLN Jul 02 '16 at 11:33
  • 1
    @SLN [This answer](http://stackoverflow.com/a/32108404/2792531) covers a lot on how Swift initializer inheritance works. – nhgrif Jul 26 '16 at 18:26
  • 1
    @SLN Because creating a Bar with `init(a: Int)` would leave `b` un-initialized. – Ian Warburton Oct 18 '16 at 17:01
  • 2
    @nhgrif Why is it necessary to reimplenent *all* of the super classes's designated initializers to unlock the convenience initializers? If only one of many superclass designated initializers were reimplemented, couldn't the compiler unlock only the convenience initializers which use it? – Ian Warburton Oct 18 '16 at 17:34
  • 2
    @IanWarburton I don't know the answer to this particular "why". Your logic in the second part of your comment seems sound to me, but the documentation clearly states that this is how it works, and throwing up an example of what you're asking about in a Playground confirms the behavior matches what is documented. – nhgrif Oct 18 '16 at 22:39
  • 1
    @IanWarburton I think the answer to your question is that it's a design issue. Yes, what you're saying could be done, but it would complicate the initialiser inheritance process a lot, and it wouldn't be clear what was available and what wasn't. Having a simple system like this makes the language easier to understand and work with. And it's already pretty complex as it is. Look how long it took to explain! Personally I think they achieved a well balanced design here. And thanks for the great post nhgrif you should be on the team to maintain or write the Swift book! Much clearer explanation. –  Nov 23 '16 at 18:59
  • 1
    Why the OP did not accept this answer? It perfectly explains why and when the `convenience` keyword is needed, and it does it in a superbly clear way. I would like the Apple documentation was equally clear.. +1 – Guido Jan 04 '17 at 21:55
  • This answer taught me so much about initializers. Especially the Base Class example. Thank you @nhgrif –  Jul 26 '17 at 14:24
  • 2
    From what I experienced being part of Swift community, the fact that the language compilation is quite complicated comparing to most other compiled languages, many people in this community lost connection to what is a programmer API and what is a compiler API. Some decisions are really questionable and `convenience` keyword is one of them. I personally see no useful insight to have it in my code. Just as with `indirect` keyword for enums, `@escaping` closures and some other keywords. – Robo Robok Feb 16 '19 at 07:36
9

Mostly clarity. From you second example,

init(name: String) {
    self.name = name
}

is required or designated. It has to initialize all your constants and variables. Convenience initializers are optional, and can typically be used to make initializing easier. For example, say your Person class has an optional variable gender:

var gender: Gender?

where Gender is an enum

enum Gender {
  case Male, Female
}

you could have convenience initializers like this

convenience init(maleWithName: String) {
   self.init(name: name)
   gender = .Male
}

convenience init(femaleWithName: String) {
   self.init(name: name)
   gender = .Female
}

Convenience initializers must call the designated or required initializers in them. If your class is a subclass, it must call super.init() within it's initialization.

Nate Mann
  • 695
  • 6
  • 17
  • 2
    So it would be perfectly obvious for the compiler what I'm trying to do with multiple initializers even without `convenience` keyword but Swift would still be bugging about it. That's not the kind of simplicity I was expecting from Apple =) – Desmond Hume Jun 17 '15 at 19:07
  • 2
    This answer doesn't answer anything. You said "clarity", but didn't explain how it makes anything clearer. – Robo Robok Feb 16 '19 at 07:42
7

Well, first thing comes to my mind is that it's used in class inheritance for code organization and readability. Continuing with your Person class, think of a scenario like this

class Person{
    var name: String
    init(name: String){
        self.name = name
    }

    convenience init(){
        self.init(name: "Unknown")
    }
}


class Employee: Person{
    var salary: Double
    init(name:String, salary:Double){
        self.salary = salary
        super.init(name: name)
    }

    override convenience init(name: String) {
        self.init(name:name, salary: 0)
    }
}

let employee1 = Employee() // {{name "Unknown"} salary 0}
let john = Employee(name: "John") // {{name "John"} salary 0}
let jane = Employee(name: "Jane", salary: 700) // {{name "Jane"} salary 700}

With convenience initializer I am able to create an Employee() object with no value, hence the word convenience

u54r
  • 1,767
  • 2
  • 15
  • 26
  • 2
    With `convenience` keywords taken away, wouldn't Swift get enough information to behave in the same exact way? – Desmond Hume Jun 17 '15 at 18:55
  • No, if you take away `convenience` keyword you can't initialize `Employee` object without any argument. – u54r Jun 17 '15 at 19:26
  • Specifically, calling `Employee()` calls the (inherited, due to `convenience`) initializer `init()`, which calls `self.init(name: "Unknown")`. `init(name: String)`, also a convenience initializer for `Employee`, calls the designated initializer. – BallpointBen Apr 14 '16 at 11:35
3

According to the Swift 2.1 documentation, convenience initializers have to adhere to some specific rules:

  1. A convenience initializer can only call intializers in the same class, not in super classes (only across, not up)

  2. A convenience initializer has to call a designated initializer somewhere in the chain

  3. A convenience initializer cannot change ANY property before it has called another initializer - whereas a designated initializer has to initialize properties that are introduced by the current class before calling another initializer.

By using the convenience keyword, the Swift compiler knows that it has to check for these conditions - otherwise it couldn't.

TheEye
  • 9,280
  • 2
  • 42
  • 58
  • Arguably, the compiler could probably sort this out without the `convenience` keyword. – nhgrif Apr 14 '16 at 02:00
  • Moreover, your third point is misleading. A convenience initializer can only change properties (and can't change `let` properties). It cannot initialize properties. A designated initializer has the responsibility of initializing all introduced properties before calling to a `super` designated initializer. – nhgrif Apr 14 '16 at 02:04
  • 1
    At least the convenience keyword makes it clear to the developer, it's also readability that counts (plus checking the initializer against the developer's expectations). Your second point is a good one, i changed my answer accordingly. – TheEye Apr 14 '16 at 10:34
2

A class can have more than one designated initializer. A convenience initializer is a secondary initializer that must call a designated initializer of the same class.

Abdul Yasin
  • 3,480
  • 1
  • 28
  • 42
1

Apart from the points other users have explained here is my bit of understanding.

I strongly feel the connection between convenience initializer and extensions. As for me convenience initializers are most useful when I want to modify (in most cases make it short or easy) initialization of an existing class.

For example some third party class that you use has init with four parameters but in your application the last two have same value. To avoid more typing and make your code clean you could define a convenience init with only two parameters and inside it call self.init with last to parameters with default values.

Abdullah
  • 7,143
  • 6
  • 25
  • 41
  • 1
    Why would Swift force me to put `convenience` in front of my initializer just because I need to call `self.init` from it? This seems redundant and kinda inconvenient. – Desmond Hume Jun 17 '15 at 19:02