on
The Swift Blog: Optionals in Swift and how to deal with them
Swift has a great, but sometimes also frustrating, way to deal with the possible absence of a value. This feature is called “Optionals” and in this post we’re going to cover some of the basics as well as how to deal with them.
What is an optional?
An optional represents the state of a value. It either exists or is nil
, e.g. it has no value. Let’s take a look at a very basic example. Swift’s Int
type has an initializer that takes in a String
and tries to convert it to an Int
. This might look like this: let convertedInt = Int(“1”)
. Now you might think why is this an optional since “1” can easily be converted to 1. And right you are, but we can never be 100% sure that the user of our app will always enter a valid number, for example he/she could try to convert “fish” to an Int
. Without optionals this conversion, or the failure of it, would result in a crash because Swift can’t convert “fish” to a valid Int
. That’s where optionals come into play. If we go back to our valid example let convertedInt = Int(“1”)
the type of the constant isn’t Int
, but Int?
which can be read as “An optional Int”. This means that convertedInt
either has a value or it is nil
. Let’s look at another example before moving on. We’re going to create a dictionary with some arbitrary data and try to access some of the values in there.
let dictionary: [String: Any] = [“name”: “Dennis”, “age”: 34]
I’ve chosen a [String: Any]
dictionary here to cover another important topic later, but in the end all dictionaries behave similarly. So, on to accessing the “name” value of the dictionary. If we try to simply subscript the dictionary we might go with something like this: let name = dictionary[“name”]
Hm, the results pannel on the right shows that the value of
name
is “Dennis”, but if we print the value we get Optional(“Dennis”)
. Why’s that? Because subscripting a dictionary always returns an optional, we know that the value is there, but the compiler doesn’t. Think of this as instructing someone to get something for you. That person might ask where to find the item you want, and you could respond “I think it’s in the top left drawer in my closet”. That’s exactly what we’re doing here. We’re telling Swift that we want the value for the key “name”, but Swift doesn’t even know that there’s a key “name” let alone a value for the key. What happens if we have a typo in our request?
As you can see the result of asking for “nam” instead of “name” returned
nil
. In a playground this isn’t such a big deal, just change the name of the key and you’re good to go. But in a production app that is deployed via the App Store you can’t just change something on the fly. Lastly, there’s a weird warning being displayed in there “Expression implicitly coerced from ‘Any?’ to ‘Any’”. What this actually means and how to get rid of it will be covered in the next sections.
Unwrapping optionals
Swift allows us to access optional values in four different ways. This technique is called “unwrapping” because the value is “wrapped” in an optional, a type on its own. Without going too much into detail here, an optional under the hood is an enum, which has two cases: The wrapped value and nil. But how do we get the value we need?
Force unwrapping
The first technique I want to discuss is force unwrapping optionals. I will cover this first so you can forget about it as soon as you’ve read this.
Let’s go back to our first example in which we try to convert a String
to an Int
.
let convertedInt = Int(“1”)
. To access the non-optional value we simply put an exclamation mark !
after the constant name: print(convertedInt!)
. This is the same as saying “I don’t care that this could be nil
, just give me the value!”.
And here’s why I want you to forget about it immediately. As stated before this conversion can fail, and force unwrapping the optional will result in a crash when it fails.
Doesn’t look too good, does it? This “Fatal Error” you see in the debug area of the playground is an indicator for you that this will result in a crash in an app btw. On to the safe way of dealing with optionals.
if, if let and guard statements
To safely unwrap optionals Swift provides us with three similar, but still pretty different options. Let’s focus on simple if
statements first.
With an if statement you can check whether the value is not nil, and if so, execute any custom logic you need.
Hm, looks like
convertedInt
is still an optional (“Optional(1)”). And rightfully so, all we do here is check if convertedInt
has a value but we’re not unwrapping the value. The only thing we’re doing here is tell Swift to only execute the code inside the if statement if it returns true
, which at least eliminates the crash when we’re trying to convert the wrong data to an Int
.
This is a good technique if you don’t care about the actual value, but what if you do?
if let
to the rescue. With an if let
statement we not only safely unwrap the optional, we also define a local constant that is available inside the if let
statement.
What this
if let
does is take the constant convertedInt
, accesses its value, and if it’s not nil
assigns the value to the local constant int
. This local constant can then be used inside the if let
to do any custom logic you need, like printing it for example. But what if the conversion fails? What’s great about if let
is that it works like a regular if
statement, in that you can chain multiple statements.
In here the first check fails because, as we learned before, “fish” can’t be converted to an
Int
, but the second one succeeds, so only the value of anotherConvertedInt
gets printed to the console. Similarly, if both conversions fail, the else
block will be executed.
On to the final option to safely unwrap optionals. This is a somewhat special case that is only useful inside functions so we’re going to write one that converts all String
s to Int
s and returns the value if the conversion succeeds, otherwise it will return nil
. The function declaration might look something like this: func convertToString(_ string: String) -> Int
.
Let’s implement our function to see how we can make the conversion work safely.
func convertToString(_ string: String) -> Int {
guard let convertedInt = Int(string) else { return 0 }
return convertedInt
}
Inside the function declaration we’re using a guard
statement to unwrap the optional Int
and assign it to a constant convertedInt
as we did with the if let
statement. The only difference here is that the constant is available outside of the guard
statement. A guard
works a little differently than an if let
in that it is used to exit a function early. What this means is that, should the conversion Int(string)
fail, we stop executing the function at that line and simply return 0. To see this in action we’re of course going to take our last example again.
As you can see in the example above, the first conversion succeeds and the correct value (1) gets printed in the console. The second one though didn’t succeed and therefore 0 will be printed. Just to go over this once more because it’s a bit complex. With a
guard
statement we allow the function to stop execution when the guard returns false
, e.g. when the conversion of the String
to an Int
fails. Because we can’t convert “fish” to a valid integer, the guard returns false
, stops execution of the function, and executes its else block, which returns 0.
Type casting
A topic that doesn’t necessarily belong here, but makes sense to be covered with optionals, is typecasting. What we mean with that is casting one type to another. Why and when you will need it? Great question, and to answer it we will take our dictionary from before to help us out.
let dictionary: [String: Any] = [“name”: “Dennis”, “age”: 34]
As said before I decided to go with a [String: Any]
dictionary here for a reason, and that reason is that we need to tell Swift a bit more concretely what this Any
will be when we’re trying to access it.
Apple’s official declaration of Any
and AnyObject
looks like this:
Any
and AnyObject
are two special types for working with nonspecific types.
Any
can represent an instance of any type at all, including function typesAnyObject
can represent an instance of any class type.
What this means is that we can use Any
to store any other type, like a String
, Bool
, Double
or any other custom types we define. The same goes for AnyObject
with the exception that it only stores classes.
Let’s move on to our dictionary and the weird compiler warning we had before.
With what we’ve learned before we can use
Any
to store any arbitrary data. And that is exactly what we’re doing here. For every String
key we store an Any
. Our dictionary declaration looks like we’re storing a String
and an Int
, but because we explicitly told Swift that this will be a [String: Any]
dictionary it returns every value we have in there as Any
. This gets more clear when we do an option-click
on the name constant like so:
let name: Any?
is Swift’s way of saying us that it returns an optional Any
because it doesn’t know what type name
is and if it even exists.
Now how would we make Any?
a String
, the correct type of the “name” key in the dictionary? This is where typecasting comes into play. As said before with this technique we can cast a value from one type to another. But because this can also fail, we need to work with optionals too.
Enough talk, more code. To convert our Any?
to String
we need to do two things. Tell Swift that we want the constant to be of type String
, and safely unwrap that result.
Ok, let’s go over this step by step:
- We declare an
if let
statement as we did before and define the local constantname
- We try to access the value for the “name” key in the dictionary
dictionary[“name”]
- We typecast the value retrieved before to a
String
using theas?
operator. You can read this as: “return the dictionary key ‘name’ as a String”. - Inside the
if let
we print the constantname
to the console when typecasting succeeded, or print an error message when it failed.
When we now take a look at the type of name
we will see that it’s a String
, an indicator that our conversion succeeded.
Now that we know how to get a “name” out of the dictionary, how do we get the “age”? I hope you’re pleased to hear that the only difference is the type you have to cast to.
If you need to access both values at once you can also go ahead and chain your requests like so:
Note however, when doing this and one of the two casts fails, the entire statement will return
false
, so use cautiously.
Conclusion
Optionals can be a scary topic, and rightfully so. The vast amount of cases when a value can be nil
is quite overwhelming. But fortunately Swift makes working with optionals pretty straightforward. I hope this post covered just enough to take away some of the fear or complexity of working with optionals, and that from now on you might even enjoy figuring out how you can work with them efficiently and - most importantly - safely.
As always, I’m @tattooedDev on Twitter. If you have any questions about this post or optionals in general please don’t hesitate to contact me.