Optionals in Swift, Part 2
Monday, March 4, 2024 3:59 PM
In Part 1 we learned that optional variables may or may not contain a value. When we declare regular variables we can just use them in our code. When we declare optional variables we must unwrap them to see what’s inside before we can use them.
Open a new Playground in Xcode and enter the following:
var someInt: Int = 5
var anotherInt: Int = 5
var someOtherInt = someInt + anotherInt
Now change anotherInt from a regular Int to an Optional Int like this:
var anotherInt: Int? = 5
You'll get an error when you try to add someInt + anotherInt. Even though we’ve initialized anotherInt with a value, the compiler knows that the value of anotherInt could change to nil. This is not the same as a value of 0. Nil means “no value” which can’t be added to 5.
The error says:
Value of optional type 'Int?' must be unwrapped to a value of type 'Int'
Unwrapping an optional is like unwrapping a present to see what’s inside.
Xcode offers a couple of suggestions for how to do that. One of them is called “force-unwrapping”, also known as “unconditional unwrapping". This is like ripping the wrapping off a present and throwing it away. Once you do that, there’s no way to rewrap it. To force unwrap an optional, we just put an exclamation mark after its name like this:
var someInt: Int = 5
var anotherInt: Int? = 5
var someOtherInt = someInt + anotherInt!
Problem solved! We’ve ripped off the wrapping, exposing the value of anotherInt which can now be added to someInt.
But there’s a big problem with this approach. Change the value of anotherInt to nil.
var someInt: Int = 5
var anotherInt: Int? = nil
var someOtherInt = someInt + anotherInt!
error: Execution was interrupted
Force-unwrapping an optional that has no value will crash your code at runtime. You shouldn’t force unwrap an optional variable in production code unless you’re absolutely certain that your optional variable contains an actual value. In most cases, if you're certain that an optional variable isn’t nil, you should just be using a regular variable, not an optional variable.
Fortunately, Swift provides safer alternatives. These allow us to peek inside an optional without ripping off the wrapping. These safer alternatives are called “nil coalescing”, “optional binding” and “optional chaining”.
We’ll get to those soon, but the simplest technique to understand is to use an if…else statement to see if an optional variable has a value or not. it looks like this:
var someInt: Int = 5
var anotherInt: Int? = nil
var someOtherInt =
if anotherInt != nil {
someInt + anotherInt!
} else {
someInt
}
We still have to force unwrap anotherInt, but now we only do so if it isn’t nil. If it has a value we use it, otherwise (else) we don't.
This works, but it’s pretty cumbersome to explicitly check if an optional variable is nil every time we need to use it in our code. Let’s look at the other techniques available to us.
Optional binding
Optional binding uses an "if let…else" syntax to automatically peek inside an optional variable to see if it’s nil. In our example it looks like this:
var someInt: Int = 5
var anotherInt: Int? = nil
var someOtherInt =
if let anotherInt {
anotherInt + someInt
} else {
someInt
}
Don’t be confused by the use of the word let here which is also used to declare a constant in Swift. You can think of the "if let…else" statement as saying, “If anotherInt isn’t nil, let someOtherInt = anotherInt + someInt. Otherwise, let someOtherInt = someInt”.
This is functionally equivalent to the if…else approach we used above, but checking to see if anotherInt is nil is built in. We don’t have to do it explicitly, and we don’t have to force unwrap anotherInt.
The nil coalescing operator
The nil coalescing operator is written as two question marks (??). It does two things:
1. Unwraps an optional variable to expose its content
2. If the content is nil, it substitutes a default value that we specify
In our example, we can use it like this:
var someInt: Int = 5
var anotherInt: Int? = nil
var someOtherInt = someInt + (anotherInt ?? 0)
Unlike force-unwrapping, the nil coalescing operator protects our code from crashing when anotherInt is nil. If it is, a value we specify (0 in this case) is used instead.
It’s important to understand that anotherInt hasn’t changed from an optional integer to an integer. The nil coalescing operator just peeks under the wrapping to see if it has a value. If it finds a value, that’s what’s used. If it finds nil, it substitutes the value we specify (in this case, 0).
Optional chaining
Optional chaining is used to unwrap optional properties, methods and subscripts. It is beyond the scope of this basic introduction, but you can read about it here.
The Great Banking App Dilemma- Solved
In Part 1 we thought through some of the logic for a banking app. We realized that we should use optionals to handle the logic needed to decide which customers have to pay a monthly fee. Refresh your memory be reviewing Part 1. We need to declare userSavingsBalance as an optional double so we can tell which customers have a savings account. If it’s nil, they’ll have to pay a monthly fee for their checking account.
Take a few moments to think about the logic we might use to safely unwrap userSavingsBalance and subtract a fee if appropriate… 🕒
Got it? Let’s apply what we’ve learned. We’ll define our variables like this:
var userCheckingBalance = 0.0
var userSavingsBalance: Double?
let monthlyFee = 3.99
var userTotalBalance = 0.0
We’ve made userSavingsBalance an optional double, because we don’t know if a savings account actually exists. We’ve also initialized userCheckingBalance and userTotalBalance with a value of 0. Our app’s logic is going to calculate the actual total balance based on data it gets from the bank for each individual customer. If a customer has a savings account, their total balance will be userCheckingBalance + userSavingsBalance. Otherwise, their total balance will be userCheckingBalance - monthlyFee. To implement this logic, we’ll need to unwrap userSavingsBalance to see if it’s nil or not.
How might we use optional binding to safely unwrap (i.e. peek inside) userSavingsBalance?
if let userSavingsBalance {
userTotalBalance = userCheckingBalance + userSavingsBalance
} else {
userTotalBalance = userCheckingBalance - monthlyFee
}
Remember that if let is like saying “If userSavingsBalance isn’t nil, do something. Otherwise do something else”. It peeks under the wrapping of userSavingsBalance to see if there’s a value or not.
How might we use a nil coalescing operator to do the same thing? This approach is even easier. All the logic we need is included in the nil coalescing operator. Instead of using if let…else we just need to do this:
userTotalBalance = userCheckingBalance + (userSavingsBalance ?? -monthlyFee)
If userSavingsBalance isn’t nil, we’re adding it to userCheckingBalance. If it is nil, we’re subtracting monthlyFee instead.
The approach you use to safely unwrap an optional depends on the specific circumstances of your app and to some extent on your personal preference.
We’ve learned what optional variables are, how they differ from regular variables, and how to take advantage of them in our code. They’re a bit more cumbersome to use, but you’ll come to depend on the power and convenience they offer.
The key concept to remember is that optionals must be unwrapped safely before their wrapped values can be used.