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.