Initializers & init() Explained in Swift
In Swift, an initializer is a special init()
function that we use to create objects of a particular class, struct or type. They’re used to “construct” an instance of the given type.
In this tutorial, we’re going to discuss how initializers work, why they’re needed, and how you can write your own. We’ll focus on the different kinds of initializers, such as convenience initializers, what the consequences are of subclassing, errors you may face in day-to-day iOS development, and much more.
Ready? Let’s go.
- What’s an Initializer In Swift?
- Default, Memberwise, Failable & Required Initializers
- Designated and Convenience Initializers
- Common Issues With Initializers
- Further Reading
What’s an Initializer In Swift?
First things first. What’s an initializer and why do we need ’em?
In Swift, an initializer is a special function that we use to create objects of a particular class, struct, or other type. Initializers are sometimes called constructors, because they “construct” objects.
Let’s take a look at an example. First, we’re creating a struct named Book
. It has 3 properties: title
, author
and pages
.
struct Book {
var title = ""
var author = ""
var pages = 0
}
Next, we’re going to create an instance of the Book
struct. Like this:
var lotr = Book()
print(lotr.pages)
// Output: 0
In the above code, the first line uses the initializer function Book()
to construct an object of type Book
. We could say that we’ve “initialized” an object Book
, and assigned it to the variable lotr
.
If you’re not familiar with Object-Oriented Programming, and concepts such as classes, make sure to check out this tutorial first: Introduction Of Object-Oriented Programming In Swift
An initializer consists of the name of the type (i.e., class, struct, etc.) followed by function parameters, wrapped in parentheses.
let constant = type name(parameters)
It’s easiest to remember that an initializer is like any other function, except that the function name is the same as the name of the type.
You use an initializer to construct an object of a particular type, like Book
. But what does that look like internally, inside the class or struct? And can we customize the way the initializer works? Yes!
Here’s an example of an initializer:
struct Book {
···
init(title: String) {
self.title = title
}
}
What’s going on here?
- Within the class or struct, the initializer has a special function name:
init()
- The above initializer has one parameter
title
of typeString
– just like any ol’ function - Inside the function, we’re assigning the value of the
title
parameter to thetitle
property - The
self
in the code refers to the current instance ofBook
, i.e. the object we’re assigning to thelotr
variable
The initializer function init()
is a function like any other Swift function, so it can have parameters, has a code body, etcetera. The init()
function implicitly-ish returns an object (see failable initializers, below).
How do you keep that Book()
and init()
apart? Here’s how:
- Outside of the struct, you use
Book()
to initialize aBook
object (see first example) - Within the struct, you use the
init()
function to customize how theBook
object is initialized (see second example)
Why don’t you play around with the code from this section, in the Swift sandbox below?
var title = ""
var author = ""
var pages = 0
init(title: String) {
self.title = title
}
}
var lotr = Book(title: "Fellowship of the Ring")
lotr.pages = 479
lotr.author = "JRR Tolkien"
print(lotr.title)
In the next few sections, we’ll discuss many more customizations and attributes you can add to initializer functions, such as the memberwise initializer, and designated initializers.
Note: We have to adhere to a few rules when working with initializers, but the most important one is that every stored property of a class or struct must have a value (or be nil
) at the end of the init()
function.
Default, Memberwise, Failable & Required Initializers
Let’s take a look at a few types of initializers that are useful in day-to-day Swift programming. They are:
- The default initializer, which will initialize an object with properties’ default values
- The memberwise initializer, which will synthesize an
init()
function based on a struct’s properties -
Failable initializers, which may return
nil
based on the parameters provided to the initializer
Default Initializer
First, the default initializer. We’ve actually already used it in the previous section. Here, check this out:
class Car {
var name = ""
}
let lambo = Car()
lambo.name = "Lamborghini Murciélago"
In the above code, we’ve created a class Car
with a property name
of type String
. On the last 2 lines, we’re initializing an instance of Car
with the Car()
initializer and assign the object to the lambo
variable. We’re also setting its name
property.
In the example, the default initializer is automatically synthesized (“generated”) based on the default property value of the Car
class. Differently said, when we initialize with Car()
, the name
parameter is set to an empty string, which is the property’s default value.
The above class definition is exactly the same as this:
class Car {
var name:String
init() {
self.name = ""
}
}
See the difference?
- The
name
property now has an explicit type annotation, with··· name:String
. Swift cannot infer the type, like it does for··· = ""
, which means you can’t use type inference to make your code more concise. - The
init()
function contains code to assign a default value to thename
property, and unlike the default value, the property and its value are “split” between the property declaration and the initializer.
Why use default values for properties, and the default initializer? Firstly, it’s more convenient. If you’re going to provide a default value, you might as well keep your code more concise. Moreover, you can use type inference, and keeping the property declaration and its default value together makes your code easier to read.
Memberwise Initializer
The memberwise initializer is a neat trick that’s only available for structs. In short, Swift will synthesize an initializer for structs based on the properties of that struct, if you don’t provide any init()
functions of your own.
Here, check this out:
struct Rectangle {
var width = 0
var height = 0
}
It’s a struct called Rectangle
with 2 properties width
and height
of type Int
. Both have a default value of 0
.
We can now initialize an instance of Rectangle
like this:
let square = Rectangle(width: 10, height: 10)
Neat, right? The above initializer Rectangle(width:height:)
is automatically generated. We’ve gotten an initializer function that has parameters for each of the properties of Rectangle
, for free! Swift automatically synthesizes an initializer for us. That initializer would look something like this:
init(width: Int = 0, height: Int = 0) {
self.width = width
self.height = height
}
The implementation of the above init(···)
function is fairly trivial, so that’s why Swift generates it for us. You’ll need to take a few caveats into account, though:
- The memberwise initializer only works for structs, and not for classes. You’ll get it “for free” for structs, but not for classes!
- If you provide your own
init()
function, you lose the memberwise initializer. A solution here is to write your own, of course.
You can use the memberwise initializer with and without default values for properties. We’ve used default values in the above example. A benefit of using default values together with the memberwise initializer, is that you keep the option to initialize an object without providing parameters. Like this:
let square = Rectangle()
square.width = 10
print(square.height) // Output: 0
Failable Initializers
A failable initializer is an initializer function that may return nil
, or may return a value. It’s the initializer equivalent of optionals. You could call ’em optional initializers.
Here, check this out:
print(age)
let year = Int("two thousand and twenty")
print(year)
What’s going on?
- The
Int(···)
initializer is failable, which means it can fail if you provide the wrong input parameters, and may returnnil
. - In the first example, we’re providing the string
"42"
, which can be successfully converted to the integer value42
; the value ofage
. - In the second example, we’re providing a text string, which cannot be converted to an integer value, so
year
will benil
. - Note that in both cases the type of the returned value is
Int?
, i.e. an optional integer.
You can declare your own failable initializer with init?()
, like this:
init?(value: ···) {
guard condition else {
return nil
}
···
}
In the above code, we’re returning nil
if the guarded condition is false
. This will cause the initializer to fail and return nil
.
Swift has a great number of failable initializers, such as the Int(···)
conversion from string to integer. Another good example are enumerations and their raw values, which may not be represented by an actual enum value.
Required Initializers
A required initializer, like its name implies, is an initializer that you must implement if you’re subclassing. We’ll look at subclassing and initialization in the next section, but required initializers are worth pointing out nonetheless.
Here’s an example:
class Vehicle {
required init() {
// this initializer must be implemented in a subclass, too
}
}
So, if we’re to make a class Car
that subclasses, we’ll need to implement the above init()
function too. Like this:
class Car: Vehicle {
required init() {
// initialize this!
}
}
Interestingly, the required
keyword implies that the subclass implementation of the initializer is actually overridden. An explicit override
modifier for the init()
function is not necessary.
You may have seen a common required initializer, namely init?(coder:)
. You’ll get the error Required initializer ‘init(coder:)’ must be provided by subclass of ‘UIViewController’ if you don’t implement the required initializer.
How do you solve that? Just define the required initializer, and call the super.init(···)
in the initializer’s body. Like this:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
Be mindful of properties you’ll still need to initialize in the above init?(coder:)
function. You’ll need to do so prior to calling super.init(···)
(see more, below).
Technically, an initializer doesn’t return a value – it merely makes sure that self
is fully initialized. Similarly, nil
may seem like it’s not “a value”, but actually nil
just means Optional.none. Failable initializers may seem paradoxical, if you consider they don’t construct the requested object, until you add optionals and Optional.none
into the equation. When an initializer fails, you just don’t get the object you asked for…
Designated and Convenience Initializers
So far so good, right? Initializers are the functions that make sure that an object is fully initialized. We’ve got that init()
, and then for a Book
struct we’d do Book(title: ···)
, and so on.
Initializing gets more complex when subclassing and inheritance come into play. In the next few sections, we’ll discuss what happens with initializers when you start subclassing.
Designated Initializers
First, let’s discuss the role of designated and convenience initializers. We’re going to start out with some code for a Circle
, like this:
class Circle
{
var radius:Double
var circumference:Double {
2 * .pi * radius
}
init(radius: Double) {
self.radius = radius
}
}
In the above code we’ve defined a class called Circle
. It has one stored property radius
of type Double
, and one computed property circumference
of type Double
. The class also has one initializer, the designated initializer, declared as init(radius:)
, which will assign the provided radius
parameter to the property of the same name.
Differently said, we can create a circle, provide its radius, and get the circumference
of the circle (2πr
). Like this:
let earth = Circle(radius: 6371.0) // km
print(earth.circumference)
// Output: 40030.17
Convenience Initializers
So far so good! Next up, we’re going to declare a second initializer. Like this:
convenience init(circumference: Double) {
self.init(radius: circumference / (.pi * 2))
}
The above init(circumference:)
initializer, called a convenience initializer, takes a circumference
parameter. Within the function body, the circumference of the circle is used to calculate its radius, which is provided to the designated initializer init(radius:)
.
Differently said, we’re creating an initializer that’s convenient to use. Some developers may want to create a circle by its radius, and others by the circle’s circumference. That’s convenient!
We’ve discussed designated and convenience initializers. Every class must have one designated initializer. Most classes typically have one, but you can add more if you want. A class can have one or more convenience initializer, and they’re typically used in a scenario where adding a convenience initializer saves you time, or makes your code clearer or easier to use.
Why didn’t we just create another init()
function? Like this:
init(circumference: Double) {
self.radius = circumference / (.pi * 2)
}
You’d have ended up with 2 designated initializers, which could have worked OK. However, by creating a convenience initializer we can delegate the initialization back to the designated initializer.
It’s as if the convenience initializer says to the designated initializer: “Look, you do the heavy lifting, I’m just here for convenience!” The benefits of initializer delegation becomes more clear when subclassing, too.
Before we continue, take note of a few “rules” for initializers:
- A designated initializer must call a designated initializer from its immediate superclass
- A convenience initializer must call another initializer from the same class
- A convenience initializer must ultimately call a designated initializer
(Source: Class Inheritance and Initialization)
Another way of remembering these rules, is that designated initializers must always delegate up, and convenience initializers must always delegate across.
Let’s see that 2nd rule in action, in the previous example. This code, right here:
convenience init(circumference: Double) {
self.init(radius: circumference / (.pi * 2))
}
See how this convenience initializer is calling the designated initializer from the same class, with self.init(···)
. The convenience initializer delegates the initialization across, to the designated initializer.
For the sake of completeness, I’ve used the word “delegation” in this section. This is different from the delegation you know in iOS development, where one class hands-off functionality to another class. In this section, “delegation” actually means to literally delegate, i.e. to tell another initializer to handle the rest of the initialization. Much like how you delegate the ordering of pizza to a friend (unless they order pineapple).
Subclassing & Initialization
Next up, we’re going to subclass the Circle
class from the previous example. We’ll define a Cylinder
, which is essentially a 3D circle with a height and volume.
Check this out:
class Cylinder: Circle
{
var height: Double
var volume:Double {
.pi * radius * radius * height
}
init(radius: Double, height: Double) {
self.height = height
super.init(radius: radius)
}
}
In the above code, we’ve declared a class Cylinder
that subclasses the Circle
class we defined earlier. The Cylinder
class will inherit properties and functions from the Circle
class.
You also see that the Cylinder
class has a height
property of type Double
, and it defines a computed property that calculates the volume
of the cylinder with πr<sup>2</sup>h
.
Then, check out that initializer. The Cylinder
class defines one designated initializer that takes both radius
and height
parameters. The height
parameter is assigned to self.height
, and the radius
parameter is provided to the super.init(···)
call.
Interesting! We’re calling the superclass initializer init(radius:)
to keep the chain of initializers intact. We either had to override an existing initializer, or create a new initializer that calls either of the other two superclass initializers. The chain remains intact, because we’ll always end up at the designated initializer init(radius:)
.
Try for yourself with the Swift sandbox below! See if you can …
- … remove the
convenience
keyword - … override the superclass initializer
- … break the chain of initializers
{
var radius:Double
var circumference:Double {
2 * .pi * radius
}
init(radius: Double) {
self.radius = radius
}
convenience init(circumference: Double) {
self.init(radius: circumference / (.pi * 2))
}
}
class Cylinder: Circle
{
var height: Double
var volume:Double {
.pi * radius * radius * height
}
init(radius: Double, height: Double) {
self.height = height
super.init(radius: radius)
}
}
let earth = Circle(radius: 6371.0)
print(earth.circumference)
let earth2 = Circle(circumference: 40030.17)
print(earth2.radius)
let pie = Cylinder(radius: 25.0, height: 5.0)
print(pie.volume)
Common Issues With Initializers
Why so much fuss over initializers? I mean, it was fun when we discussed that initializers were like functions, but did we have to dive into designated, convenience, chains and what not? Yes, we had to!
See, initializers have an important task in iOS/Swift development. Initializers must make sure that objects are properly created. Swift has a bunch of rules that ensure that objects cannot be improperly initialized.
Rules mean – especially if you’re unaware – that you’ll bump into errors and bugs here and there. That’s why we’re going to discuss a few error messages you might come across in your day-to-day iOS development. We could not have done that had we not discussed how initializers work exactly.
Let’s get to it!
Must Call a Designated Initializer of the Superclass
Remember the rules for initializers that we discussed? When subclassing, initializers must ultimately call a designated initializer of the superclass.
Consider the Circle
and Cylinder
classes we created before. If we add a new initializer to the Cylinder
class, it must call a designated initializer of the superclass Circle
. Like this:
class Cylinder: Circle {
···
init(volume: Double) {
···
super.init(radius: ···)
}
}
Double-check with yourself that init(radius:)
is in fact the designated initializer of the Circle
class, i.e. the superclass of the Cylinder
class.
How do you solve this error? Make sure to call the designated initializer of the superclass. First, find the designated initializer. Then, call it in your own subclass initializer implementation.
Class Has No Initializers
Alright, the meaning of this error is simple: a class you’re using does not have any initializers. This typically means 2 things:
- You’ve not given a property of a class a default value, which means it’s not initialized. The solution here is to give that property a default value.
- You’ve not added an actual
init()
initializer to the class, when you needed to.
Here’s a quick example:
class Car {
var name:String
}
The above name
property of the Car
class doesn’t have a default value, and the class has no initializers. You can either provide a default value, and subsequently use the generated default initializer, or code an init()
function yourself.
You can also come across the Class has no initializers bug when the order of operations inside the init()
function is wrong. This is especially apparent in subclassing UIViewController.
Consider that you’ve got a view controller with a text
property that does not have a default value. Like this:
class MyViewController: UIViewController {
var text:String
···
}
Then, make sure that the text
property is initialized prior to calling super.init(···)
. Like this:
required init?(coder aDecoder: NSCoder) {
self.text = ""
super.init(coder: aDecoder)
}
Property Not Initialized At super.init Call
The Property not initialized at super.init call is similar to the one above. One of the rules of working with initializers, is that a designated initializer must initialize any of its properties prior to calling super.init(···)
.
This is exactly what happened in the previous error. We had to initialize self.text
before calling super.init(···)
.
Why? Well, since the superclass initializer cannot be trusted to initialize properties of a subclass, the subclass will need to make sure to initialize its own new properties prior to delegating to the superclass.
You could run into issues with properties that are used at a later point, i.e. after initialization, and don’t have a sensible value prior. One option would be to make those properties optionals, or even implicitly unwrapped optionals.
Note: In older versions of Swift, this error, and the previous one, can prompt the following error message: Return from initializer without initializing all stored properties. The solution is the same: make sure properties are initialized before the end of init()
, and before calling a superclass initializer.
Initializer Does Not Override a Designated Initializer From Its Superclass
This error happens if you attempt to override a convenience initializer of a superclass. Convenience initializers need to delegate across, i.e. they need to call a designated initializer from the same class. This cannot be done in a subclass, so the solution is simple: drop the override
keyword, and make sure to call a designated initializer.
Must Call a Designated Initializer of the Superclass ‘UIViewController’
This error message is similar to the ones we’ve already discussed. It pops up when attempting to subclass the UIViewController
class (or similar subclasses, like UIView
). You’ve chosen the wrong initializer to call. Make sure to call the designated initializer.
How do you find the designated initializer? Check the documentation for UIViewController
, find its initializers, and find the initializer not marked with convenience
.
Further Reading
Awesome! We’ve come a long way. We started out by just discussing what initializers are, what they’re for, and how you can write your own init()
function. We focused on different types of initializers, and the relationship between designated and convenience initializers, and subclassing. Neat!
Want to learn more? Check out these resources: