Optionals in Swift
Behind the scenes of one of Swift’s biggest differentiators
No matter how long your Swift journey has been so far, you definitely heard about Optionals or at least saw some ?
or !
in your code. In this post, I’m going to show you what Optionals are, how they work under the hood and some best practices for dealing with them.
Although this post uses some more advanced terms like Generics, you should still be able to understand the key parts of this app if you never heard of such a thing.
What’s a statically typed language?
Swift is a statically typed language. This means that in order for the compiler to work successfully, every variable, constant, and function must have their type declared in advance (before the compilation).
// No type: Compiler Error ⚠️
var x
// Explicitly typed integer
var x: Int = 0
// Implicitely typed integer
var y = 0
As demonstrated in the example above, you can either explicitly specify the type, or let the compiler infer it based on its value.
Declaring a type is a one-time-thing. After declaring a variable, constant, or function, its type cannot change. The following could not be compiled:
var x = 0
x = "Evil String"
Strongly typed
Furthermore, Swift is strongly typed. This means you cannot e.g. call a function that expects a String with an Int value. Because it’s statically typed the language already knows all the types before compile time, so it can shout at you before even doing the hard work.
What’s an Optional?
Let’s imagine we work on an app that deals with GitHub user profiles. Each user must have a username and optionally have a bio. The JSON representation of a user could look like this:
{
"username": "slashmo",
"bio": "Freelance iOS-Developer"
}
If we were to write a Swift user type our first attempt could be this:
struct User {
let username: String
var bio: String
}
let user = User(username: "slashmo", bio: "Freelance iOS-Developer")
But, as stated above the bio of a user could also be empty, or nil
in Swift terms. Our current User
struct is not able to provide us with this functionality:
let user = User(username: "slashmo", bio: nil)
The reason for this is actually pretty simple. As you now know Swift is strongly typed, so it expects a value of type String
for the bio
property, because we explicitly said so in its declaration. And because nil
is not a String
value the compiler doesn’t want to compile it.
The Optional type
For such use cases, Swift has a specific type, the Optional<Wrapped>
. It’s an enumeration that’s generic over Wrapped
. The enumeration consists of two cases, .none
and .some(Wrapped)
.
Put simply, an Optional is like a box that either holds a value or nothing. The Optional works with every other type by wrapping the optional value inside. This means you can have Optional Strings, Optional Ints, Optional Users and even Optional Optionals. 🔥
And because it’s just another type we can use it to declare variables, constants, and functions, just like String
, Int
, …
Applied to the GitHub example our User
struct now looks like this:
struct User {
let username: String
var bio: Optional<String>
}
let user = User(username: "slashmo", bio: .none)
let nerd = User(username: "slashmo", bio: .some("Freelance iOS-Developer"))
Syntax Sugar
To be honest, the above doesn’t look as Swifty as we’re used to. Especially the .some
case with its associated String is unnecessarily verbose because it’s quite obvious that we have some value if a String is associated with it 😊. Also, it’s a bit tedious to always declare optional types as Optional<Wrapped>
.
There is a far simpler way to declare Optionals and set their values:
struct User {
let username: String
var bio: String?
}
let user = User(username: "slashmo", bio: nil)
let nerd = User(username: "slashmo", bio: "Freelance iOS-Developer")
By suffixing any type with ?
we wrap it inside an Optional type. And because Optional
conforms to ExpressibleByNilLiteral
the .none
case can be expressed as nil
. Optional
also has an initializer taking a non-optional instance of Wrapped
which lets us skip the .some
if we have a value. Much nicer, isn’t it? 💅
How can we work with optional values?
Now that we’re able to declare Optionals the next step is to actually work with them. Let’s say, e.g. that in our imaginary GitHub app we want to show the bio on a users profile if it’s set.
Before we can use a value wrapped inside an Optional we first have to unwrap it (get it out of the box).
There are several ways to unwrap an Optional
. I’ll explain them one by one and compare the different approaches along the way.
Forced unwrapped Optional
The first one I’m going to show you is the forced unwrapped Optional.
bioLabel.text = user.bio!
The !
after the property tells the compiler that we are 100% sure that user.bio
holds a value, or in other words that it is not nil
. The result of this expression is the unwrapped String
which we can assign to the labels text
property.
If, in the above snippet, the users’ bio were nil our program would crash and throw this message:
Unexpectedly found nil while unwrapping an Optional.
To avoid this from happening we could first check if the Optional
is nil
and then force-unwrap the value only if the check was successful:
if user.bio != nil {
bioLabel.text = user.bio!
} else {
// bio is nil, hide the label
}
Again, this is not very Swifty because we basically repeat ourselves unnecessarily. First, we check that the Optional
is not nil
, but then we have to force-unwrap it anyways.
Optional binding
As hinted above, there’s a more elegant solution. Optional binding allows us to bind a value of an Optional
, if one exists, to a constant or variable.
if let bio = user.bio {
bioLabel.text = bio
} else {
// bio is not available in here, because user.bio is nil
}
Now, we don’t have an explicit condition but rather an assignment to a new constant called bio
. This constant is only available inside the if
-block and it holds the unwrapped value. Not only is this more readable than the previous example, but it also gives us the benefit of only unwrapping once. Let’s say we wanted to use the users’ bio twice. In the case of force-unwrapping, we’d have to write the !
for each usage. With optional binding, we unwrap once and use the new constant every time we want to use the value.
Optional binding with early exit
Depending on the use case, the if let
-block can get quite large, often times larger than the else
-block. In this case, we might want to use a guard let
instead which gives us the benefit of not having to go one indentation level deeper just to access an Optional
s value.
guard let bio = user.bio else {
// bio is not available here, because user.bio is nil
return
}
bioLabel.text = bio
As you can see, the bio
constant is now available in the same scope. Keep in mind that with guard
statements we always have to have an else clause, which comes first, hence the name early exit. This else clause has to exit unless we call a non-returning function like fatalError()
.
Nil-coalescing
What if we want to show a default text if the users’ bio is nil
?
if let bio = user.bio {
bioLabel.text = bio
} else {
bioLabel.text = "No bio"
}
This looks quite verbose. We repeated the assignment part, only to set a different value depending on the Optional
’s case. Normally, we would use a ternary operation in such a case to clean things up a bit:
bioLabel.text = user.bio != nil ? user.bio! : "No bio"
Although this works it’s also not that clear, because we first check if the Optional
is nil
and then force-unwrap the value if that’s not the case. Swift provides another way by introducing the Nil-coalescing operator.
bioLabel.text = user.bio ?? "No bio"
This has the exact same result but now does the nil
-check and unwrapping implicitly.
Optional chaining
For the sake of this example, let’s add an optional friend
to our User
type:
struct User {
// ...
var friend: User?
}
This friend is of type Optional<User>
and therefore also has an optional bio. In another view of our imaginary app, we might want to show that friends bio if it’s available. With the power of optional binding we could come up with this:
if let friend = user.friend {
if let friendBio = friend.bio {
friendBioLabel.text = friendBio
}
}
However, this doesn’t scale very well as it adds another level of indentation, although in the second scope we only use the friend
constant to unwrap the second optional, their bio
. Let’s think of this as a chain instead.
user.friend?.bio
Voilà, now we have optional chaining. Because friend
is an Optional
we can’t directly access it’s bio
. Instead, we start an optional chain by writing a ?
between each part of the chain.
We can use this optional chain in combination with optional binding and end up with a pretty elegant solution:
if let friendBio = user.friend?.bio {
friendBioLabel.text = friendBio
} else {
// either friend or bio is nil. Which one is undefined
}
Optional chains can be endless and also contain non-optional parts in between optional parts.
Implicitly unwrapped Optional
As you may already know, to successfully initialize an instance of any type, all its non-optional properties have to be set before returning from the initializer. In other words: If we want to create an instance without providing a default value to a property that property has to be an Optional
. This means that we’d always have to unwrap this Optional
where we want to use it. Because this can be very cumbersome in use-cases where we are 100% sure that a property holds a value (but it was not provided by us), instead of explicitly unwrapping it for every usage we can implicitly unwrap it by suffixing the type with !
instead of ?
.
@IBOutlet weak var bioLabel: UILabel!
If you’re an iOS-Develop working in interface builder you know this kind of line. It creates a connection between your visual interface and your code. Now, because we cannot set the value itself it needs to be an Optional
. If a connection is established the value will never be nil
, so it’s safe to implicitly unwrap it. However, if the value is nil
, e.g. if we forgot to create the connection, and we use the property the program will crash, giving us the same error message as the forced unwrapped Optional
.
Summary
Now that you know what Optionals
are all about and how to work with them let’s recap some of the key points of this post.
Optional<Wrapped>
is anenum
with two cases:none
&some(Wrapped)
- Optionals are declared by suffixing another type with
?
:String?
- Instead of writing
.none
we usenil
:user.bio = nil
- Instead of writing
.some(<value>)
we use<value>
:user.bio = "Bio"
- To access an
Optional
’s value we have to unwrap it - By suffixing an
Optional
variable / constant with!
we force-unwrap it, which causes the program to crash if it wasnil
instead - With the help of optional binding we are able to bind the unwrapped value to a new constant / variable to use it inside a block:
if let friend = user.friend { print(friend) }
- Optional binding works with both
if
&guard
- The nil-coalescing operator allows us to assign either the unwrapped value or a default value depending on the case of the
Optional
:label.text = user.bio ?? "Default"
- With optional chaining we can create a call chain of optional variables, constants & functions:
user.friend?.bio?.uppercased
- If we precede a type with
!
instead of?
we create an implicitly unwrappedOptional
, which force unwraps theOptional
every time we use it automatically