Tidbit: Convenience Initializers

Recall from the first lecture when we made a BankAccount class like this:

class BankAccount {
  
  var accountHolderName: String
  var accountBalance: Double
  var accountPIN: UInt
  
  init(name _name: String, initialBalance _bal: Double, PIN _PIN: UInt) {
    self.accountHolderName = _name
    self.accountBalance = _bal
    self.accountPIN = _PIN % 10000
  }
  
  convenience init() {
    self.init(name: "Bob Loblaw", initialBalance: 0, PIN: 0000)
  }
  
  func deposit(amount: Double) {
    self.accountBalance += amount
  }
  
  func withdraw(amount: Double) {
    self.accountBalance -= amount
  }
}
    

I got mostly flustered by convenience init because that was a relatively new feature added to Swift--at the time, it seemed pretty inconvenient (haha). What exactly does convenience mean, though?

Well, first we need to define what a designated initializer. Designated initializers are meant to be the primary initializers for a class. For an initializer to be a designated initializer, it must fully initialize all members of a class. It must also call any superclass initializers, when applicable.

In other words, no matter how your class is initialized, it must funnel through a designated initializer, in order to make sure that all members are initialized, including any members introduced by superclasses. Every class must have at least one designated initializer--in some cases, this may be inherited from a superclass (think: when?).

So, in the above example, init(name: String, initialBalance: Double, PIN: UInt) is a designated initializer because it fully initializes all members of the class. Calling a superclass's initializer is not necessary because there is no superclass for BankAccount

However, suppose your boss is a jerk. And suppose your boss wants you to implement a few different initializers. Specifically, he says you should implement the following different initializers:

init(name _name: String, initialBalance _bal: Double, PIN _PIN: UInt) {
    // TODO
}

init(initialBalance _bal: Double, PIN _PIN: UInt) {
    // TODO
    // name should be "John Doe"
}

init(PIN _PIN: UInt) {
    // TODO
    // name should be "John Doe"
    // initial balance should be 0.
}

init() {
    // TODO
    // name should be "John Doe"
    // initial balance should be 0
    // initial PIN should be 1234
}
    

Your boss would then be a veritable jerkface, but you know a quick and easy way to implement that would look like this:

init(name _name: String, initialBalance _bal: Double, PIN _PIN: UInt) {
    self.accountHolderName = _name
    self.accountBalance = _bal
    self.accountPIN = _PIN % 10000
}

init(initialBalance _bal: Double, PIN _PIN: UInt) {
    self.accountHolderName = "John Doe"
    self.accountBalance = _bal
    self.accountPIN = _PIN % 10000
}

init(PIN _PIN: UInt) {
    self.accountHolderName = "John Doe"
    self.accountBalance = 0
    self.accountPIN = _PIN % 10000
}

init() {
    self.accountHolderName = "John Doe"
    self.accountBalance = 0
    self.accountPIN = 1234
}
    

However, this is quite a bit of copy-and-pasting. Can you imagine if this class had a dozen members? They would all need to be initialize. Remember, a class must have all of its members fully initialized--you can't skip any! This could become massive and terrible to maintain. What if you add a member? All your initializers have to change, then!

The problem can be easily solved by calling initializers from other initializers, right? You'd want to have something like this:

init(name _name: String, initialBalance _bal: Double, PIN _PIN: UInt) {
    self.accountHolderName = _name
    self.accountBalance = _bal
    self.accountPIN = _PIN % 10000
}

init(initialBalance _bal: Double, PIN _PIN: UInt) {
    self.init(name: "John Doe", initialBalance: _bal, PIN: _PIN)
}

init(PIN _PIN: UInt) {
    self.init(name: "John Doe", initialBalance: 0, PIN: _PIN)
}

init() {
    self.init(name: "John Doe", initialBalance: 0, PIN: 1234)
}
    

This makes your life a little bit easier, and it makes your intent a little bit clearer. The last 3 initializers though, aren't considered designated initializers. They don't do all of the work to initialize all of the class members--that work gets funneled into the first initializer.

The bottom three initializers are called convenience initializers, because they rely on designated initializers to get the job done for them. Convenience initializers are also called secondary, or support, initializers. They must call a designated initializer at some point (meaning, you can call convenience initializers within convenience initializers, as long as a designated initializer is called at some point).

Xcode becomes upset if you don't tell it what you're planning on doing. So, if you have a convenience initializer in your code, you must mark it with a convenience keyword, like so:

init(name _name: String, initialBalance _bal: Double, PIN _PIN: UInt) {
    self.accountHolderName = _name
    self.accountBalance = _bal
    self.accountPIN = _PIN % 10000
}

convenience init(initialBalance _bal: Double, PIN _PIN: UInt) {
    self.init(name: "John Doe", initialBalance: _bal, PIN: _PIN)
}

convenience init(PIN _PIN: UInt) {
    self.init(name: "John Doe", initialBalance: 0, PIN: _PIN)
}

convenience init() {
    self.init(name: "John Doe", initialBalance: 0, PIN: 1234)
}
    

The Initialization Funnel

There are three basic rules you must follow when working with initializers (taken straight from the Apple Docs on the subject):

This is sometimes summed up as ``designated initializers delegate up, convenience initializers delegate across''. Why do they say that? This picture should help (also straight from Apple's docs):

I call this the initialization funnel, because initialization must always funnel its way up to the base superclass of everything--it cannot and should not be stopped or hindered in any way. It means that a class and all of its superclasses must have all of its members initialized, because they all must call designated initializers. So, we should never have the problem of an uninitialized value!