A convenience initializer is a secondary initializer that must call a designated initializer of the same class. It is useful when you want to provide default values or other custom setup. A class does not require convenience initializers.
Convenience initializers are secondary, supporting initializers for a class. You can define a convenience initializer to call a designated initializer from the same class as the convenience initializer with some of the designated initializer's parameters set to default values.
A designated initializer fully initializes all properties introduced by that class and calls an appropriate superclass initializer to continue the initialization process up to the superclass chain. Convenience initializers are secondary, supporting initializers for a class.
Conversely, if you write a subclass initializer that matches a superclass convenience initializer, that superclass convenience initializer can never be called directly by your subclass, as per the rules described above in Initializer Delegation for Class Types.
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 callself.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:
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
:
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:
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.
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.
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
According to the Swift 2.1 documentation, convenience
initializers have to adhere to some specific rules:
A convenience
initializer can only call intializers in the same
class, not in super classes (only across, not up)
A convenience
initializer has to call a designated initializer
somewhere in the chain
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With