The Swift Blog: Optionals in Swift and how to deal with them

February 13, 2019 - 10 minute read -
swift

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 Strings to Ints 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 types
  • AnyObject 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:

  1. We declare an if let statement as we did before and define the local constant name
  2. We try to access the value for the “name” key in the dictionary dictionary[“name”]
  3. We typecast the value retrieved before to a String using the as? operator. You can read this as: “return the dictionary key ‘name’ as a String”.
  4. Inside the if let we print the constant name 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.

Syntax hightlighting powered by Splash