1

I have read responses to similar questions here, but I am still puzzled regarding what would be the best implementation here, why, and is there any difference.

In this implementation, I have two initializers for the class

class Person1 {
  var name: String
  var age: Int
  var nationality: String

  init(name: String, age: Int,  nationality: String) {
    self.name = name
    self.age = age
    self.nationality = nationality
  }

  init(name:String, age: Int) {
    self.name = name
    self.age = age
    self.nationality = "Canadian"
  }
}

When I create an instance, I have the option of choosing from one or the other. If I choose the second one, I get the default nationality set as Canadian

  let p1 = Person1(name: "Stewart", age: 40)

If I swap out that second initializer with the following convenience initializer,

convenience init(name: String, age: Int) {
    self.init(name: name, age: age, nationality: "Canadian")
}

I can create the same instance the same way and get the same result. So what is the difference? I am finding difficulty finding a good example where a convenience initializer is the only way to accomplish creating a particular instance of a class that can't be accomplished by a second initializer like I did in the first example.

Stewart Lynch
  • 875
  • 9
  • 26
  • 2
    The difference is really only interesting when you are subclassing. – matt Mar 11 '20 at 02:31
  • 2
    Why don’t you simply add a default value to your nationality? `init(name: String, age: Int, nationality: String = "Canadian") {` no need to create 2 initializers – Leo Dabus Mar 11 '20 at 02:54
  • I could do that, but it doesn't answer my question. I am trying to determine if there is a benefit to one way over another. – Stewart Lynch Mar 11 '20 at 03:01
  • @matt, so do you mean that in my implementation, one way is as good as the other because it makes not difference unless I am subclassing the class? – Stewart Lynch Mar 11 '20 at 03:02
  • It doesn't make a difference compelling enough to be interesting. You are saying "there is nothing to choose between them". You're right, because in the example you're using it's a matter of taste. But if we were subclassing or being subclassed, it would make a huge difference whether an initializer is a designated initializer or a convenience initializer. – matt Mar 11 '20 at 03:55
  • Hi, check this link https://useyourloaf.com/blog/adding-swift-convenience-initializers. I think he has given a clear explanation. – AlbinMrngStar Mar 11 '20 at 04:12

1 Answers1

1

As it is, having your two initializers is Bad Code™. The way to express that exact example making use of the most appropriate Swift language feature, is, as Leo Dabus said, to use a default parameter value.

class Person1 {
  var name: String
  var age: Int
  var nationality: String

  init(
    name: String,
    age: Int,
    nationality: String = "Canadian"
  ) {
    self.name = name
    self.age = age
    self.nationality = nationality
  }
}

A convenience initializer is what you use when another initializer exists, and will save you code duplication.

For example, Let's say that you add a superclass, with a required init:

class Person0 {
  var name: String
  var age: Int

  required init(
    name: String,
    age: Int
  ) {
    self.name = name
    self.age = age
  }
}

You can't just do this; Swift doesn't consider this to satisfy the requirement:

class Person1: Person0 {
  var nationality: String

  required init(
    name: String,
    age: Int,
    nationality: String = "Canadian"
  ) {
    self.nationality = nationality
    super.init(
      name: name,
      age: age
    )
  }
}

Instead, you can either duplicate, like this:

  required init(
    name: String,
    age: Int
  ) {
    nationality = "Canadian"
    super.init(
      name: name,
      age: age
    )
  }

  init(
    name: String,
    age: Int,
    nationality: String
  ) {
    self.nationality = nationality
    super.init(
      name: name,
      age: age
    )
  }

…or use one designated initializer, which actually does the work, and forward the required init along to it:

  required convenience init(
    name: String,
    age: Int
  ) {
    self.init(
      name: name,
      age: age,
      nationality: "Canadian"
    )
  }

  init(
    name: String,
    age: Int,
    nationality: String
  ) {
    self.nationality = nationality
    super.init(
      name: name,
      age: age
    )
  }

I am finding difficulty finding a good example where a convenience initializer is the only way to accomplish creating a particular instance of a class that can't be accomplished by a second initializer like I did in the first example.

You can always duplicate code. A convenience initializer will never be the only way. In the simplest cases, like I'm showing here, you'll only have a partial line of duplication, per initializer. That's not a big deal. But deduplicating piles up, with inheritance.

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. – https://docs.swift.org/swift-book/LanguageGuide/Initialization.html

So, for example, when you use a convenience + designated combo, you get to skip rewriting a required init.

e.g.

Person2(name: "Terrance Phillip", age: 42)

compiles, from this:

class Person2: Person1 {
  var flag: String

  override convenience init(
    name: String,
    age: Int,
    nationality: String
  ) {
    self.init(
      name: name,
      age: age,
      nationality: nationality,
      flag: ""
    )
  }

  init(
    name: String,
    age: Int,
    nationality: String,
    flag: String
  ) {
    self.flag = flag
    super.init(
      name: name,
      age: age,
      nationality: nationality
    )
  }
}

That's also the same person as this:

Person3()

…given this:

class Person3: Person2 {
  convenience init() {
    self.init(
      name: "Terrance Phillip",
      age: 42
    )
  }
}

As a guideline, use 1. default parameters and 2. convenience initializers whenever possible. (And as an extension of 2, transform designated initializers into convenience ones, in subclasses.) You will have to maintain less code.