Swift Fundamentals

Working With Optionals

Swift Fundamentals

Optionals are an integral aspect of Swift development. They highlight a core concept of the Swift language. Safety.

At first glance, optionals look inconvenient and a necessary evil of Swift development. That's true if safety isn't high on your list of priorities. Unless you embrace optionals and what they represent, working with Swift will be a struggle and it will end in frustration.

Optional Binding

Optionals are not part of the language to annoy you. Working with optionals can be elegant. In the previous episode, I showed you how you can access the value stored in an optional. We used the exclamation mark to force unwrap the value stored in an optional.

That's not how you should interact with optionals, though. There are a handful of patterns that make working with optionals easier. Let's start with optional binding. This is the example we ended with in the previous episode.

var message: String? = "Hello World"

if message != nil {
    print(message!)
} else {
    print("No Value to Print")
}

It looks verbose and not very elegant. We need five lines of code instead of one. Safety comes at a cost.

A more common pattern is to use optional binding to safely unwrap the value of the optional. In the condition of the if statement, we assign the value of message to a constant named unwrappedMessage.

var message: String?

if let unwrappedMessage = message {
    print(unwrappedMessage)
} else {
    print("no value found")
}

The condition of the if statement is known as optional binding. Swift inspects the value of message and assigns the value to the unwrappedMessage constant if and only if message contains a value.

I'd like to zoom in on three details that aren't immediately obvious if you're new to optional binding.

First, the condition of the if statement looks like a regular assignment. Remember that we can't simply assign the value of an optional to another variable or constant. Optional binding is different, though. Swift inspects the optional and safely unwraps its value if it contains a value.

Second, the condition of an if statement needs to evaluate to true or false, and that's exactly what an optional binding does. If the value stored in message is successfully assigned to the unwrappedMessage constant, the condition evaluates to true. If the optional binding fails, the condition evaluates to false and the else clause is executed.

Third, the unwrappedMessage constant becomes available in the if clause if the optional binding succeeds. That's why we can pass it to the print(_:separator:terminator:) function.

The solution is still verbose, but it's easy to read and understand. Optional binding is very powerful. Let's take a look at a more complex example of optional binding.

We declare three optionals, name, high, and low. We use optional binding to safely unwrap the values stored in the optionals. The optional bindings are separated by commas.

var name: String? = "AAPL"
var high: Float? = 110.34
var low: Float? = 99.23

if let name = name, let high = high, let low = low {
    print("\(name): \(high) - \(low)")
}

The if clause of the if statement is executed if every optional binding is successful. This example underlines the power and versatility of optional binding.

Optional Chaining

Optional chaining is another convenient concept to work with optionals. To illustrate how optional chaining works, we define a structure, User, with one property, first, and one method, makePayment(). We explore structures, properties, and methods in more detail later in this series.

struct User {

    let first: String

    func makePayment() {
        print("\(first) made a payment.")
    }

}

If a user makes a payment, we print a message to the console. Let's put the User structure to use. We define a variable, user, of type User?.

var user: User?

Because user is an optional, we can't guarantee that it contains a User instance. We could unwrap the value stored in user and invoke the makePayment() method only if we have access to a User instance.

var user: User?

if let validUser = user {
    validUser.makePayment()
}

It's a safe solution, but it doesn't look very appealing. It's verbose and it gets old very quickly. Optional chaining simplifies this drastically.

Let's see what happens if we invoke the makePayment() method on the user variable without unwrapping it first. The compiler throws an error and a message is printed to the console.

Playground execution failed:

error: Optionals.playground:11:1: error: value of optional type 'User?' not unwrapped; did you mean to use '!' or '?'?
user.makePayment()
^
    ?

This isn't surprising. Xcode gives us a hint, though. It suggests to append an exclamation mark or a question mark to the user variable. Let's append a question mark to the user variable.

user?.makePayment()

The error disappears and nothing is printed to the console. The empty console isn't surprising since the user variable doesn't hold a valid User instance.

By appending a question mark to the user variable we use optional chaining to invoke the makePayment() method. How does this work? The makePayment() method is only invoked if the user variable contains a valid value. If the optional is equal to nil, the makePayment() method cannot and is not invoked.

This becomes clear if we assign a valid User instance to the user variable and invoke the makePayment() method again.

var user: User?

user?.makePayment()

user = User(first: "Bart")

user?.makePayment()

A message is printed to the console because the makePayment() method was successfully executed.

Bart made a payment.

There's a good reason why this pattern is referred to as optional chaining. Let's refactor the makePayment() method a little bit. Notice that the method returns a value of type String?. The example is a bit contrived, but it helps illustrate the flexibility of optional chaining.

struct User {

    let first: String

    func makePayment(amount: Float) -> String? {
        if amount > 0.0 {
            return "\(first) made a payment of $\(amount)."
        }

        return nil
    }

}

Let's invoke the new makePayment(amount:) method and perform an operation on the return value. Notice that we can chain multiple queries.

user?.makePayment(amount: 10.0)?.uppercased()

Xcode automatically inserts a question mark because it sees that the makePayment(amount:) method returns an optional. If any of the links in the chain fails, the chain fails. We can prove this by passing 0.0 to the makePayment(amount:) method. The result of the expression is equal to nil.

user?.makePayment(amount: 0.0)?.uppercased()

Nil-Coalescing Operator

Optional binding and optional chaining are essential when working with optionals in Swift. But Swift has another trick up its sleeve. Swift defines an operator I wish I had at my disposal when I was working with Objective-C, the nil-coalescing operator. The idea is very simple. Take a look at this example.

var message: String?
let body: String

I would like to assign the value stored in message to the body variable, but, if message doesn't contain a value, I would like to assign a default value to body. There are several approaches to solve this problem. We can use an if statement and optional binding to safely unwrap the value stored in message.

var message: String?
let body: String

if let message = message {
    body = message
} else {
    body = "A Default Message"
}

print(body)

While it may seem as if this is the recommended approach, there's a more elegant solution. The nil-coalescing operator reduces five lines of code to a single line of code without compromising safety or elegance.

var message: String?
let body: String = message ?? "A Default Message"

print(body)

Swift inspects the value of message and assigns that value to body if message contains a value. If message is equal to nil, it falls back to the value defined after the two question marks, the nil-coalescing operator.

Remember that Swift is a strongly typed language, which means that the value stored in message needs to be of the same type as the default value provided to the nil-coalescing operator. We can simplify this example by omitting the type specifier of the body constant. The compiler uses type inference to deduce that body is of type String.

var message: String?
let body = message ?? "A Default Message"

print(body)

Optional Types

The variable message is of type String?. Remember that we define the type of the value stored in the optional's container. It's important to understand that String? and String are different types. Let me clarify this with an example.

We declare a variable, message1, of type String? and we declare a variable, message2, of type String. Even though we assign the same string to both variables, they are of different type.

var message1: String? = "Hello World"
var message2: String = "Hello World"

We can prove this with another example. We can assign the value of message1 to another variable, message3, which is also of type String?. But notice that we cannot assign the value of message1 to message2 because they are of different type.

var message1: String? = "Hello World"
var message2: String = "Hello World"

var message3: String? = message2
message2 = message1

The compiler throws an error and prints a message to the console.

Playground execution failed:

error: Optionals.playground:5:12: error: value of optional type 'String?' not unwrapped; did you mean to use '!' or '?'?
message2 = message1
           ^
                   !

Even though message1 and message2 are of different type, Swift tries to help us whenever it can. In this example, we compare message1 and message2 in an if statement. Swift understands that we want to compare the value wrapped in message1 with the value of message2. In other words, it compares two strings.

var message1: String? = "Hello World"
var message2: String = "Hello World"

if message1 == message2 {
    print("They are the same.")
} else {
    print("They are different.")
}

What's Next?

Even though Swift is a strongly typed language, it tries to be flexible whenever it can. Optional binding and optional chaining are core concepts you need to become familiar with when working with optionals. The nil-coalescing operator is the cherry on the cake for writing elegant code that's easy to read.

Next Episode "Tuples"

Stop Writing Swift That Sucks

Download the Swift Patterns I Swear By