Unwrapping, nil coalescing, map(), flatMap(), and more.
Optionals are a fundamental part of Swift development, but at first they seem a bit confusing, and the more you learn the more you realize there’s a lot of subtlety to using them well.
In this article we’re going to look at a range of questions about optionals, starting off with easy questions that newcomers ask, then working our way forward to topics that will interest more experienced developers.
SPONSORED Ready to dive into the world of Swift? try! Swift Tokyo is the premier iOS developer conference will be happened in April 9th-11th, where you can learn from industry experts, connect with fellow developers, and explore the latest in Swift and iOS development. Don’t miss out on this opportunity to level up your skills and be part of the Swift community!
Sponsor Hacking with Swift and reach the world's largest Swift community!
Optionals represent something that has no value. For example, consider this code:
let names = ["Margaret", "Matthew", "Megan"]
let position = names.index(of: "Mxyzptlk")
That creates an array of names and looks up the position of a name that doesn’t exist. What value should position
have when that code runs?
Int.max
, which is harder to remember and also impossible – any such array of strings would take up too much memory!None of those are good solutions, which is where optionals come in: they represent the absence of a value. An empty optional integer isn’t equal to 0, -1, 16777216, or any other number – it has nothing at all.
Optionals are created by appending ?
to your type name. So, Int
must always contain a real integer, but Int?
might be an integer or might be missing, and String
must always contain a real string, but String?
might be a string or might be missing.
Optionals are best thought of as boxes that may or may not contain a value.
Optionals let us represent missing, unknown, or deleted data. If you have a User
struct with an age of 0, that might mean they have just been born or it might mean you don’t know – it’s hard to tell. By having making that same value optional it becomes clearer: when it’s 0 it’s a newborn baby, and when it’s nil you just don’t know the age.
Because optionals may or may not be empty, Swift won’t let you us them freely. If an optional is empty – nil
, in Swift – then it can’t be used in your code. For example:
To keep you from using nil
values by accident, Swift makes us unwrap the optionals: the process of looking inside to see whether there is a value there or not. If there is a value then Swift will unwrap it (take it out of the optional) and send it back to us as a non-optional, but if there isn’t then the test will fail.
The most common way to unwrap is using if let
syntax like this:
let name: String? = "Taylor"
if let unwrappedName = name {
print("Hello, \(unwrappedName)!")
} else {
print("Hello, anonymous!")
}
That looks inside the value of name
. If it has a value, it will be unwrapped (taken out of the optional) and placed into unwrappedName
as a non-optional string, then the contents of the condition will be executed – the name will be printed. If there is no name inside, then “Hello, anonymous!” will be printed.
To be clear: name
was an optional string, and unwrappedName
is a non-optional string – it definitely has a value, even if that’s just the empty string ""
.]
The other common way to unwrap optionals is using guard let
syntax, which is similar to if let
except:
guard
unwrap fails you must exit the current scope. This usually means existing your method, but it might also mean exiting a condition or loop. Swift will refuse to compile unless you do this.guard
unwrap succeeds, the unwrapped value exists after the guard
block finishes. This is the opposite of if let
, where the unwrapped value only exists inside the first code block.Here’s how guard let
looks:
func printName(_ name: String?) {
guard let unwrappedName = name else {
print("Hello, anonymous!")
return
}
print("Hello, \(unwrappedName)!")
}
You will use both regularly.
As you’ve seen if let
and guard let
unwrap optionals safely: they look inside, run some code if the optional has a value, and run some code if the optional doesn’t have a value.
Force unwrapping skips that check and immediately extracts a value from optionals so you can use it immediately. It’s triggered by appending an exclamation mark after an optional variable, like this:
let name: String? = "Taylor"
let unwrappedName = name!
That might sound like a nice shortcut, and there are some times you’ll find it useful, but what would happen in this code:
let name: String? = nil
let unwrappedName = name!
That’s trying to unwrap an optional that doesn’t have a value. If you try to force unwrap an optional that is empty, your app will crash immediately. It will just die, with no way to recover. As a result, !
is sometimes called the crash operator.
Force unwraps are mainly used in two places:
I can’t do much about the former other than encourage folks to think twice about force unwrapping, but the latter is more complex. Sometimes you can find ways of communicating your extra knowledge to the compiler so that it can check your work more carefully, but other times there is no graceful way and trying to work around optionals can just make your code harder to read.
For example, the window of an iOS app is optional, but if that’s set to nil
then you’re done for because something is really broken. Or if you try to load a view controller from your storyboard and it can’t be found, that’s a fundamental error in your project – there’s no point trying to show users an empty view controller instead.
As you’ve seen, optionals might contain a value but might also be empty, and to avoid crashes and weird behaviors Swift makes you unwrap them using if let
, guard let
, or even force unwraps if you’re feeling brave.
Implicitly unwrapped optionals also might contain a value or be empty, but Swift won’t make you unwrap them before using them. That is, you can use them directly as if they weren’t optional at all. However, as with force unwrapping, if you try to use an implicitly unwrapped optional that happens to be empty, your app will crash immediately.
Implicitly unwrapped optionals are created by using !
in the type rather than ?
. So, String
must always have a real string, String?
is an optional string (might have a value or might not, but Swift makes us unwrap it before use), and String!
is an implicitly unwrapped optional string (might have a value or might not, but Swift won’t make us unwrap it before use.)
Implicitly unwrapped optionals are common in places where you know something the compiler doesn’t: that a specific object will start life as being empty, but afterwards will always have a value. It’s common with Interface Builder outlets, like these:
@IBOutlet var imageView: UIImageView!
When your view controller is initially created that image view will be nil
because it hasn’t been made yet. But as soon as you try to show the view controller, its underlying view will be created and so will the image view - and will continue to exist until the view controller is finally destroyed.
Note: Implicitly unwrapped optionals are actually implemented as regular optionals under the hood, but they have a special flag that marks them as not needing to be unwrapped. This flag is attached specifically to your implicitly unwrapped optional, and gets lost as soon as you take a copy of it.
For example:
let name: String! = "Taylor"
let nameCopy = name
In that instance, name
is an implicitly unwrapped optional (String!
), but nameCopy
is a regular optional (String?
).
Apart from when you create them explicitly, optionals are created in three other ways: optional typecasts (as?
), optional try (try?
), and failable initializers (init?()
). All three of these are common in Swift, so it’s worth taking the time to learn them thoroughly.
First, as?
is used when you want to convert one type to another. For example, if Swift thinks you have an instance of UIViewController
but you know you actually have an instance of MyCustomViewController
, you might write this:
if let myVC = viewController as? MyCustomViewController {
// we have a MyCustomViewController
} else {
// we have something else
}
Many of UIKit’s APIs return these kinds of superclasses when really we know we should get something specific. For example, dequeuing a cell from a table always returns a UITableViewCell
, but if you have a custom class you’ll want to typecast that using as?
.
Second, try?
is useful when you want to convert a throwing function into a non-throwing function that returns an optional. If your function call succeeds your optional will have the function’s return value inside, but if the function call throws an error then you’ll get back an optional containing nil
.
For example, trying to create a string from the contents of a file is a throwing function, but if you just wanted to know whether it worked or not you could use try?
like this:
if let saved = try? String(contentsOfFile: "saved.txt") {
print(saved)
} else {
print("No saved text.")
}
Finally, failable initializers create optional instances of a type, allowing the initializer to return nil
if there was a problem.
For example, we could create a User
struct that gets created with an integer ID number like this:
struct User {
var id: Int
}
However, what if we wanted to enforce the rule that ID numbers must always be greater than zero? The struct above will get Swift’s automatic memberwise initializer, but it won’t validate the id
number that is passed in.
To fix that we could write our own failable initializer, like this:
struct User {
var id: Int
init?(id: Int) {
if id < 0 {
return nil
}
self.id = id
}
}
Creating a User
will now always return an optional user, because our ID number might get rejected.
Note: as?
, try?
, and init?()
all have force variants: as!
, try!
, and init!()
. If you use as!
and your typecast fails then your app crashes. If you use try!
fails and your call throws an error, then – you guessed it! – your app crashes. If you use init!()
you’ll just get back an implicitly unwrapped optional.
Optional chaining allows us to work with several optionals at once. For example, if we had an optional user
, which had an optional middleName
property, we can uppercase it like this:
let upperMiddle = user?.middleName?.uppercased()
Here’s what will happen when that code is run:
user
is nil then upperMiddle
will be set to nil
immediately – the rest of the line is ignored.user
has a value then Swift will look at its middleName
property – if that is nil then upperMiddle
will be set to nil
immediately and the rest of the line ignored.user
and its middleName
property have values, then the middle name will be uppercased and stored in upperMiddle
.Because the code can return nil
at two different places, upperMiddle
will end up being an optional string.
You can use optional chaining as much as you want in a single line of code: a?.b?.c?.d?.e?.f
is fine, for example.
Optional optionals are a good sign that you need to rethink your code a little. If an optional integer is like a box that might have a number or might not, then an optional optional integer is that box inside another box.
Optional optionals are written with two question marks, like this: Int??
or String??
. And yes, before you ask, you can even make optional optional optionals, but honestly anything beyond a regular optional is a bad idea.
It’s common to want to check whether an optional has a value or not, but if it doesn’t provide a default instead. In Swift we do this with the nil coalescing operator, ??
, which means “if the following optional has a value then unwrap it and send it back to me, otherwise use this other value instead.
As an example, here’s some code we wrote earlier that prints of two strings:
let name: String? = "Taylor"
if let unwrappedName = name {
print("Hello, \(unwrappedName)!")
} else {
print("Hello, anonymous!")
}
Using nil coalescing we could collapse that down to just two lines:
let unwrappedName = name ?? "anonymous"
print("Hello, \(unwrappedName)")
We could even put the nil coalescing into the string interpolation to make one line, like this:
print("Hello, \(name ?? "anonymous")")
You can use nil coalescing anywhere you have optionals, which includes as?
, try?
, init?()
, and more. Again, here’s some code from earlier:
if let saved = try? String(contentsOfFile: "saved.txt") {
print(saved)
} else {
print("No saved text.")
}
With nil coalescing we could collapse it down to this:
let saved = (try? String(contentsOfFile: "saved.txt")) ?? "No saved text."
print(saved)
There’s a second form of the nil coalescing operator that lets us try multiple alternative values. For example, suppose you had a dictionary of values from a web server, describing someone’s address. Here’s part of it:
let address = [
"postCode": "BA1 2BW"
]
You’re not quite sure what their postal code is, so you want to try a few options: “zip”, “zipCode”, and “postCode”. Using nil coalescing you can try them all like this:
let postCode = address["zip"] ?? address["zipCode"] ?? address["postCode"]
As soon as one of them is found, the rest of the line is ignored and the value is returned.
However, because all three might fail the value you get back will be an optional. If you want to get a non-optional back, all you need to do is make sure the last item in your chain always returns a value, like this:
let postCode = address["zip"] ?? address["zipCode"] ?? address["postCode", default: "Unknown"]
The simplest way of checking for empty optionals is to compare them against nil
, like this:
var name: String? = "Frodo"
if name == nil {
print("Sneaky little hobbitses!")
}
In switch
blocks you can also use question marks to check for the presence of a value, while also unwrapping whatever was found. For example:
var name: String? = "Frodo"
var password: String? = "h0bbit0n"
switch (name, password) {
case let (name?, password?):
print("Hello, \(name). Your password is \(password)")
case let (username?, nil):
print("Please enter a password, \(username).")
default:
print("Who are you?")
}
The same question mark syntax also works with arrays. If you use for case let someValue? in someArray
then Swift will check each optional, ignore any that are nil, but unwrap any that aren’t nil and execute your loop body for that item.
For example:
let hobbits: [String?] = ["Frodo", nil, "Samwise", nil, "Meriadoc"]
for case let hobbit? in hobbits {
print("\(hobbit) left Hobbiton.")
}
Under the hood, optionals are actually Swift enums that have two cases: .none
for empty optionals, and .some(WrappedValue)
if they do have a value.
Although there is some special compiler work around things like if let
and guard let
, a lot of the functionality of optionals themselves is actually implemented in Swift code – you can read the source code right here. It’s not very long, and I think you’ll learn a lot!
Here’s how optionals are defined:
public enum Optional<Wrapped>: ExpressibleByNilLiteral {
case none
case some(Wrapped)
}
You just saw how optionals were defined, and there’s a conformance right there for ExpressibleByNilLiteral
so that we can make optionals that have an initial value of nil
.
However, optionals also use conditional conformance to conform to both Equatable
and Hashable
but only if the type they contain conform to those protocols.
This means you can write code like this:
let age1: Int? = 30
let age2: Int? = 40
if age1 == age2 {
print("Same.")
} else {
print("Different.")
}
That compares two optionals directly, and works fine because:
age1
and age2
are nil then the result is true: they are the same.This functionality is automatically enabled through conditional conformances, so if you want to use it with your custom types make sure your type conforms to Equatable
or Hashable
.
Optionals have two methods that are helpful for transforming their contents: map()
and flatMap()
. Both of them have map()
somewhere in the name, which a functional programming term that means “transform”: you provide them with a transformation closure that does something to your optional data.
Let’s take a look at map()
first, because it’s easiest. You If you call map()
on an optional that has a value, the value gets removed from its optional container, transformed using your closure, then put back into a new optional.
For example, here’s a getUserAge()
method that returns an optional integer for times when we didn’t know their age:
func getUserAge() -> Int? {
return 38
}
let age = getUserAge()
We can use map()
to transform it into a string like this:
let ageString = age.map { "Your age is \($0)" }
Remember, if age
was empty then the map()
call just returns nil
straight away, but if it has a value – like it does here – then it will be transformed into a string then rewrapped in a new optional. So, to use that optional we can still use nil coalescing, like this:
print(ageString ?? "We don't know your age.")
So, the complete code is this:
func getUserAge() -> Int? {
return 38
}
let age = getUserAge()
let ageString = age.map { "Your age is \($0)" }
print(ageString ?? "We don't know your age.")
If we wanted to implement just the last three lines without map()
we’d need to use the longer if let
form, like this:
if let age = getUserAge() {
print("Your age is \(age)")
} else {
"We don't know your age."
}
So that’s map()
: it transforms an optional’s value if it has one, or returns nil
otherwise. But let’s look at an example where map()
struggles:
let age: String? = "38"
let ageInt = age.map { Int($0) }
That creates an age value using an optional string, for example if the user typed a number into a text box. It then uses map()
to transform it into integer. When that code runs, what data type is ageInt
?
It’s not Int
.
It’s not even Int
.
It’s Int??
– an optional optional integer. Like I said earlier, optional optionals are a good sign that you need to rethink your code a little, because to use that you need to unwrap it twice.
This happens because map()
wraps its return value in an optional, but our transformation closure creates an integer from a string, which is a failable initializer. So, the initializer creates an optional, and that gets wrapped in another optional by map()
.
This is where flatMap()
comes in: if your transformation closure returns an optional, it gets collapsed into a single optional – either the whole thing exists or nothing exists. The code is almost identical:
let age: String? = "38"
let ageInt = age.flatMap { Int($0) }
Now ageInt
is a regular optional integer, which is much more usable.
The rule for which to use is really simple: if your transform closure returns an optional use flatMap()
, otherwise use map()
.
Optionals are one of the most important safety features of Swift, making sure we never try to work with empty memory by accident.
With features like guard let
, nil coalescing, map()
and flatMap()
, and, yes, even force unwrapping sometimes, we can make optionals much more pleasant to use, so we can focus on actually using the data they contain.
SPONSORED Ready to dive into the world of Swift? try! Swift Tokyo is the premier iOS developer conference will be happened in April 9th-11th, where you can learn from industry experts, connect with fellow developers, and explore the latest in Swift and iOS development. Don’t miss out on this opportunity to level up your skills and be part of the Swift community!
Sponsor Hacking with Swift and reach the world's largest Swift community!
Link copied to your pasteboard.