Working With Guard in Swift

Exit Early With Guard

Working With Guard in Swift
1 Exit Early With Guard 08:17
2 Guard Patterns, Tips, and Tricks 06:27
Stop Writing Swift That Sucks

DISCLAIMER: No Rocket Science Involved

Join 20,000+ Developers Learning About Swift Development

Download the 4 Swift Patterns I Swear By

One of the key differences between junior and more senior developers is how they use the tools they are given. While a junior developer looks for any tool that gets the job done, a more senior developer looks for the tools that can get the job done and selects the most appropriate tool.

This very much applies to Swift's guard statement. Developers new to the language don't quite understand why you would ever want to use a guard statement. It looks confusing and it is similar to Swift's if statement. There are other developers that use a guard statement when an if statement is more appropriate.

In this episode, I want to show you how and when to use the guard statement. I also discuss when the if statement is a more appropriate choice.

How Does Swift's Guard Statement Work

The first years of Swift development were a bit rough for many of us. Some of the best features were added in later releases of the Swift language. The guard statement was one of those later additions.

The guard statement is one of my favorite constructs of the Swift language and I hope this episode illustrates why that is. Let's start with an example. This is what a typical guard statement looks like. I use this pattern time and time again in the prepare(for:sender:) method.

// MARK: - Navigation

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    guard let identifier = segue.identifier else {
        return
    }

    switch identifier {
    case Segue.Note:
        guard let destination = segue.destination as? NoteViewController else {
            return
        }

        guard let indexPath = tableView.indexPathForSelectedRow else {
            return
        }

        // Configure Destination
        destination.note = notes[indexPath.row]
    default:
        break
    }
}

The anatomy can be confusing if you are unfamiliar with the guard statement. Let's focus on the first guard statement of the prepare(for:sender:) method.

// MARK: - Navigation

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    guard let identifier = segue.identifier else {
        return
    }

    ...
}

Unlike an if statement, a guard statement always has an else clause. The else clause of a guard statement is executed when the expression of the guard statement evaluates to false. This also includes the scenario in which optional binding fails. This is something that trips up many developers that are new to the guard statement.

When I am reading code, I replace the guard keyword with the words "make sure that". Let's apply this to the first guard statement of the prepare(for:sender:) method. The guard statement now reads likes this. Make sure that the optional binding of the identifier property succeeds or else return from prepare(for:sender:) method. That makes more sense. Right?

// MARK: - Navigation

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    guard let identifier = segue.identifier else {
        return
    }

    ...
}

In this example, the else clause is executed if the segue's identifier property doesn't have a value. The body of the else clause is simple. It contains a return statement, which means it returns from the prepare(for:sender:) method.

That is another important feature of the guard statement. The else clause is required to transfer control in such a way that it exits the scope in which the guard statement is defined.

// MARK: - Navigation

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    guard let identifier = segue.identifier else {
        return
    }

    ...
}

If the expression of the guard statement evaluates to true or, in this example, if the optional binding of the identifier property succeeds, code execution continues after the guard statement and, more importantly, the variables and constants bound in the expression of the guard statement are available after the guard statement. That is why we can access the value of the identifier constant in the switch statement.

// MARK: - Navigation

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    guard let identifier = segue.identifier else {
        return
    }

    switch identifier {
        ...
    }
}

If you are still not grasping the meaning of the guard statement, then let me replace the guard statement with an if statement.

// MARK: - Navigation

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if let identifier = segue.identifier {
        switch identifier {
            ...
        }
    }
}

This example illustrates that a guard statement can be used to avoid, what is commonly referred to as, the pyramid of doom or multiple levels of indentation. That is something we can avoid by taking advantage of the guard statement.

We can avoid nested if and switch statements by exiting as early as possible. That brings us to the true purpose of the guard statement.

Keep It Simple

Despite your best efforts, some functions and methods are complex. You need to use a bunch of if and switch statements, which can become messy very quickly. It also makes testing a pain.

To keep the implementation of complex functions and methods simple, it helps to exit as early as possible. This is what that looks like in Objective-C.

- (NSArray *)fetchNotes:(NSArray *)notes {
    if (!self.isReachable) return nil;
    if (!self.isConnected) return nil;
    if (!notes || !notes.count) return nil;

    // Fetch Notes
    // ...

    return @[];
}

This is the only use case in which I omit the curly braces of an if statement in Objective-C. It improves readability and it doesn't pose any risk as long as you keep the if statement on one line.

This pattern is a good start to simplify the implementation of the fetchNotes: method because it reduces the need for nested if statements. This is what the implementation would look like if we were to nest the if statements.

- (NSArray *)fetchNotes:(NSArray *)notes {
    if (self.isReachable && self.isConnected) {
        if (notes && notes.count) {
            // Fetch Notes
            // ...

            return @[];
        }
    }

    return nil;
}

The implementation in Swift looks similar. It is a bit cleaner because we don't need to use the self keyword. But notice that we are stuck with at least one level of nesting because we need to safely unwrap the value stored in the notes parameter.

private func fetch(notes: [String]?) -> [Note]? {
    if !isReachable { return nil }
    if !isConnected { return nil }
    if let notes = notes, notes.count > 0 {
        return []
    }

    return nil
}

Exit Early With Guard

The Swift team added the guard statement in Swift 2 to resolve these issues. The guard statement isn't very different from a regular if statement. The main difference is that the guard statement is designed to exit early from the current scope if the expression of the guard statement evaluates to false.

Let's revisit the implementation of the prepare(for:sender:) method I showed you earlier. Because we access the value of the segue's identifier property in the switch statement, we are only interested in segues that have an identifier. If a segue doesn't have an identifier, we can return immediately from the prepare(for:sender:) method.

// MARK: - Navigation

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    guard let identifier = segue.identifier else {
        return
    }

    switch identifier {
    case Segue.Note:
        guard let destination = segue.destination as? NoteViewController else {
            return
        }

        guard let indexPath = tableView.indexPathForSelectedRow else {
            return
        }

        // Configure Destination
        destination.note = notes[indexPath.row]
    default:
        break
    }
}

The same applies to the switch statement. In the body of the first case, we downcast the destination view controller to an instance of the NoteViewController class. If that fails, it makes no sense to execute the remaining code of the body of the case. And the same is true for the third guard statement of the prepare(for:sender:) method.

Designed For Elegance and Convenience

I mentioned earlier that the guard statement is similar to the if statement, but there is an important difference. What makes the guard statement powerful is that the values that are safely unwrapped through optional binding are available in the scope in which the guard statement is defined. That feature allows us to use the value stored in the identifier constant in the switch statement.

// MARK: - Navigation

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    guard let identifier = segue.identifier else {
        return
    }

    switch identifier {
        ...
    }
}

Let's refactor the implementation of the fetch(notes:) method by replacing the if statements with guard statements. Remember that the expression of the guard statement needs to evaluate to true to avoid entering the else clause of the guard statement. This is what we end up with. Notice that the expression of the guard statement can contain multiple conditions, separated by a comma.

private func fetch(notes: [String]?) -> [Note]? {
    guard isReachable else { return nil }
    guard isConnected else { return nil }
    guard let notes = notes, !notes.isEmpty else { return [] }

    // Fetch Notes
    // ...

    return nil
}

Guard or If

Even though the guard statement looks very appealing, it shouldn't be your first or default choice. The if statement should be your default choice. Use the guard statement only if you guard against something. In other words, if a condition must be met for the rest of the code to make sense, then the guard statement is the best choice. Most of the time, you will find yourself using an if statement.

What I often recommend is to start with an if statement and, once everything is working as expected, refactor the implementation by adding one or more guard statements if that makes sense.

From the moment you are using nested if statements it may be time to break the function or method up into several smaller functions or methods, or, to introduce one or more guard statements.

Take a look at this example. If the table view asks us for the height of the first row of a section, we return 60.0, otherwise we return 44.0.

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    if indexPath.row == 0 {
        return 60.0
    } else {
        return 44.0
    }
}

We could rewrite this implementation by using a guard statement instead. The result is identical, but this is not how the guard statement should be used. It only complicates the implementation. While I am sure some developers would argue that it is fine to use a guard statement, this is not what it was designed for.

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    guard indexPath.row > 0 else {
        return 66.0
    }

    return 44.0
}

If you want to make the implementation of the tableView(_:heightForRowAt:) method a bit more elegant, then remove the else clause and put the if statement on one line. There's no need for a guard statement in this example.

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    if indexPath.row == 0 { return 66.0 }
    return 44.0
}

I have also seen some developers do the following. It looks odd at first, but it is very easy to read.

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    if indexPath.row == 0 { return 66.0 }
    else { return 44.0 }
}

Patterns, Tips, and Tricks

In the next episode, I share with you several patterns and tricks that take advantage of the guard statement.

Stop Writing Swift That Sucks

DISCLAIMER: No Rocket Science Involved

Join 20,000+ Developers Learning About Swift Development

Download the 4 Swift Patterns I Swear By
Next Episode "Guard Patterns, Tips, and Tricks"