Swift Patterns

Failable Initializers

Swift Patterns
1 Namespaces 07:20
2 Abstract Classes 05:55
3 Builders 11:54
4 Failable Initializers 09:47
Download Your Free Copy of
The Missing Manual
for Swift Development

The Guide I Wish I Had When I Started Out

Join 20,000+ Developers Learning About Swift Development

Download Your Free Copy

If you are new to Swift, then you may be wondering what a failable initializer is and why you would ever use one. In this episode, I show you how to create a failable initializer and I hope I can convince you of their benefits. I use failable initializers in every project I work on and you may be surprised when I say that you do too.

What Is a Failable Initializer?

As the name suggests, a failable initializer is an initializer that can fail. You recognize a failable initializer by the question mark after the init keyword. Take a look at the following example. We define a struct with name Video. It defines two properties, position of type TimeInterval and duration of type TimeInterval.

import Foundation

struct Video {

    // MARK: - Properties

    let position: TimeInterval
    let duration: TimeInterval

}

A struct automatically receives a memberwise initializer. Let's create a Video object by invoking its memberwise initializer.

let video = Video(position: 15.0, duration: 60.0)

Even though the Video struct is as simple as it gets, it introduces a number of problems. Let's define a computed property, progress, of type Float. The computed property returns the relative progress by dividing the value of position by the value of duration.

import Foundation

struct Video {

    // MARK: - Properties

    let position: TimeInterval
    let duration: TimeInterval

    // MARK: -

    var progress: Float {
        Float(position / duration)
    }

}

The value progress returns is equal to 0.25.

let video = Video(position: 15.0, duration: 60.0)

video.progress // 0.25

What happens if we create a Video object with 0.0 as the duration? Dividing a number by zero isn't a good idea. Xcode shows us that the value of progress is infinity.

Dividing a number by zero isn't a good idea in Swift.

The values we passed to the initializer of the Video struct are invalid. The duration of a video should always be greater than 0.0 and it should be greater than the position of the player.

We could validate the values we pass to the initializer, but that can become tedious if the Video struct is used in multiple places in the codebase. It is more convenient to put the Video struct in charge of deciding which values are valid and which values are invalid. If the values passed to the initializer are invalid, initialization should fail. In short, we need a failable initializer. It shouldn't be possible to create a Video object with invalid values.

How to Implementation a Failable Initializer?

A failable initializer needs to meet two requirements. We add a question mark after the init keyword and we return nil if initialization fails. That's it. Let's implement a failable initializer for the Video struct.

Because we implement an initializer for the Video struct, Swift won't create a memberwise initializer for the Video struct. That is a good thing because Video objects should only be created using the failable initializer we are about to implement.

The initializer defines two parameters, position of type TimeInterval and duration of type TimeInterval. In the body of the initializer, we assign the values of the parameters to the properties.

import Foundation

struct Video {

    // MARK: - Properties

    let position: TimeInterval
    let duration: TimeInterval

    // MARK: -

    var progress: Float {
        Float(position / duration)
    }

    // MARK: - Initialization

    init(position: TimeInterval, duration: TimeInterval) {
        self.position = position
        self.duration = duration
    }

}

The initializer we implemented is identical to the memberwise initializer Swift creates for the Video struct if it doesn't define an initializer. We make the initializer failable by appending a question mark to the init keyword.

import Foundation

struct Video {

    // MARK: - Properties

    let position: TimeInterval
    let duration: TimeInterval

    // MARK: -

    var progress: Float {
        Float(position / duration)
    }

    // MARK: - Initialization

    init?(position: TimeInterval, duration: TimeInterval) {
        self.position = position
        self.duration = duration
    }

}

The initialization of the Video object fails if the initializer returns nil. The values that are passed to the initializer need to meet a few requirements. The value of the position parameter needs to be greater than or equal to 0.0 and its value needs to be less than or equal to the value of the duration parameter. We use a guard statement and the closed range operator to validate the value of the position parameter.

// MARK: - Initialization

init?(position: TimeInterval, duration: TimeInterval) {
    guard (0...duration).contains(position) else {
        return nil
    }

    self.position = position
    self.duration = duration
}

Even though an initializer doesn't return a value, by returning nil we communicate that the initialization of the Video object failed.

We use the same pattern to validate the value of the duration parameter. Because we already verified that the value of the position parameter needs to be less than or equal to the value of the duration parameter, we only need to verify that the value of the duration parameter is greater than 0.0.

// MARK: - Initialization

init?(position: TimeInterval, duration: TimeInterval) {
    guard (0...duration).contains(position) else {
        return nil
    }

    guard duration > 0.0 else {
        return nil
    }

    self.position = position
    self.duration = duration
}

We can combine the guard statements into a single guard statement like this.

// MARK: - Initialization

init?(position: TimeInterval, duration: TimeInterval) {
    guard
        duration > 0.0,
        (0...duration).contains(position)
    else {
        return nil
    }

    self.position = position
    self.duration = duration
}

The downside of a failable initializer is that it creates an optional value of the type it initializes. This means we need to use optional chaining to access the progress of the Video object.

let video = Video(position: 15.0, duration: 0.0)

video?.progress // nil

Because the values we pass to the initializer are invalid, the video constant is equal to nil. The benefit of using a failable initializer is that we can be sure every Video object is initialized with a valid position and duration.

Benefits of Failable Initializers

It may seem inconvenient that a failable initializer creates an optional value of the type it initializes, but that downside doesn't outweigh the benefits of failable initializers.

Data Integrity

As I mentioned earlier, we could validate the values we use to create the Video object before we pass them to the initializer. The problem with that solution is that it doesn't scale and is prone to errors. It would result in code duplication and code that is tedious to maintain.

By implementing a failable initializer, we make the Video struct the gatekeeper. The Video struct defines which values are valid and which values are invalid. The benefit is that validation is encapsulated in the Video struct. A single code path is responsible for validating the values we use to create Video objects and we can guarantee that no Video objects can be created with an invalid position and/or duration.

Other objects that create or work with Video objects don't need to worry about these details. This results in clean and readable code and no code duplication.

Testability

By encapsulating these responsibilities in the Video struct, unit testing becomes trivial. We don't need to write unit tests for every scenario the Video struct is used in. We only need to write unit tests for the initializer of the Video struct. Let me show you how easy that is.

I have created a project that includes the Video struct. We use an XCTestCase subclass to unit test the Video struct. The testInitialization() method unit tests the failable initializer we created earlier. Let's first unit test the happy path. We create a number of Video objects and use the XCTAssertNotNil(_:) function to make sure the initializer creates a valid Video object.

import XCTest
@testable import Videos

final class VideoTests: XCTestCase {

    // MARK: - Tests for Initialization

    func testInitialization () throws {
        XCTAssertNotNil(Video(position: 0.0, duration: 10.0))
        XCTAssertNotNil(Video(position: 1.0, duration: 10.0))
        XCTAssertNotNil(Video(position: 10.0, duration: 10.0))
    }

}

Because we are unit testing a failable initializer, we also need to write unit tests for the unhappy path. The idea is similar. We create a number of Video objects and use the XCTAssertNil(_:) function to make sure the initialization fails if the values we pass to the initializer are invalid.

import XCTest
@testable import Videos

final class VideoTests: XCTestCase {

    // MARK: - Tests for Initialization

    func testInitialization () throws {
        XCTAssertNotNil(Video(position: 0.0, duration: 10.0))
        XCTAssertNotNil(Video(position: 1.0, duration: 10.0))
        XCTAssertNotNil(Video(position: 10.0, duration: 10.0))

        XCTAssertNil(Video(position: 0.0, duration: 0.0))
        XCTAssertNil(Video(position: 11.0, duration: 10.0))
        XCTAssertNil(Video(position: -1.0, duration: 10.0))
        XCTAssertNil(Video(position: 0.0, duration: -10.0))
    }

}

It is important to verify that the unit tests cover every possible scenario. Xcode's built-in code coverage support can help you with this, but it isn't a silver bullet.

Common Examples of Failable Initializers

Even if failable initializers are new to you, chances are that you have already used failable initializers. There are plenty of types that define one or more failable initializers.

Take a look at this example. We define a constant and assign the string 5 to it. We can convert the string to an integer by passing the string to an initializer Int defines. That initializer is failable and that isn't surprising. The conversion from a string to an integer only succeeds if the string can be interpreted as an integer.

let number = "5"
Int(number) // 5

The initialization fails if we pass a string to the initializer that cannot be converted to an integer.

let nan = "abcdef"
Int(nan) // nil

Classes, structs, and enums can define failable initializers. Swift automatically generates a failable initializer for enums with raw values. The failable initializer accepts the raw value as its only argument. Take a look at this example. We define an enum, ImageFormat, that defines three cases jpg, png, and svg. The raw values are of type String.

enum ImageFormat: String {

    // MARK: - Cases

    case jpg
    case png
    case svg

}

We can create an ImageFormat object by passing a string to the failable initializer Swift automatically generates for the ImageFormat enum. Notice that the casing of the string we pass to the initializer matters.

ImageFormat(rawValue: "png") // png
ImageFormat(rawValue: "PNG") // nil

What's Next?

Failable initializers have many benefits and I use them frequently in the projects I work on. Keep in mind that initialization can fail for many reasons, not only invalid parameter values. Failable initializers can reduce code duplication, prevent unexpected errors, and improve the testability of a project. The only downside is that a failable initializer creates an optional value of the type it initializes.

Download Your Free Copy of
The Missing Manual
for Swift Development

The Guide I Wish I Had When I Started Out

Join 20,000+ Developers Learning About Swift Development

Download Your Free Copy