Back to blog

Initializers & init() Explained in Swift


Aasif Khan
By Aasif Khan | Last Updated on March 26th, 2024 6:35 am | 6-min read

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.

Describe your app idea
and AI will build your App

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 type String – just like any ol’ function
  • Inside the function, we’re assigning the value of the title parameter to the title property
  • The self in the code refers to the current instance of Book, i.e. the object we’re assigning to the lotr 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 a Book object (see first example)
  • Within the struct, you use the init() function to customize how the Book object is initialized (see second example)

Why don’t you play around with the code from this section, in the Swift sandbox below?

struct Book {
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:

  1. The default initializer, which will initialize an object with properties’ default values
  2. The memberwise initializer, which will synthesize an init() function based on a struct’s properties
  3. 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 the name 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:

let age = Int(“42”)
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 return nil.
  • In the first example, we’re providing the string “42”, which can be successfully converted to the integer value 42; the value of age.
  • In the second example, we’re providing a text string, which cannot be converted to an integer value, so year will be nil.
  • 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:

  1. A designated initializer must call a designated initializer from its immediate superclass
  2. A convenience initializer must call another initializer from the same class
  3. 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 πr2h.

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

class Circle
{
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:

  1. 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.
  2. 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!


Aasif Khan

Head of SEO at Appy Pie

App Builder

Most Popular Posts