Maybe It’s There, Maybe It Isn’t: Optionals

A few times so far, we’ve seen our result values log messages include the term “optional,” a behavior we haven’t explained yet. But it’s time to deal with it, because optionals are one of Swift’s defining features. Create a new playground called OptionalsPlayground and delete the "Hello, playground" line, as usual.

We’ll start by adding the sizeInMm dictionary from a few sections back, since that’s something that started giving us this “optional” stuff.

 let​ sizeInMm = [
 "iPhone 7"​: 138.1,
 "iPhone 7 Plus"​ : 158.1,
 "iPad Air 2"​ : 240.0,
 "iPad Pro"​ : 305.7]

Looking at this, we can see that sizeInMm["iPhone 7"] should evaluate to 138.1, which is a Double, meaning a double-precision floating-point number.

Well, that’s great, but what if we evaluate sizeInMm["iPhone 8"], a key not in the dictionary? If our return value is a Double, what’s the right value for its size? 0? -1? Some huge positive or negative value that we just interpret as a “no-value”?

Swift has a better answer for this: optionals. An optional is a type that represents two different things: whether there’s a value at all and, if so, what the value actually is.

It turns out that dictionaries always return optionals for their values, as we can see by inspecting the type(of:) of the value we get back:

 let​ size7 = sizeInMm[​"iPhone 7"​]
 type(of:size7)

In the results pane, this shows size7 as 138.1 and the type(of:) as Optional<Double>.Type.

Now let’s try the same thing with a nonexistent value, like the size of the fictional iPhone 8:

 let​ size8 = sizeInMm[​"iPhone 8"​]
 type(of:size8)

This shows us a size of nil and the dynamicType of Optional<Double>.Type. It’s the same type as before—a Double optional—only this time there isn’t a value.

Unwrapping Optionals

As you might imagine, we’re frequently going to be concerned with whether an optional value is nil, and when it’s not, we often want to get to the value itself. We do this through a process called unwrapping. To “unwrap” a Double optional like the values in our dictionary means to take an Optional<Double> and turn it into just a normal Double.

One way to unwrap is to use the force-unwrap operator, which is the ! character. Try it out on size7:

 type(of:size7!)

This force-unwraps size7 to be a non-optional type, and then gets its type(of:). The results pane shows the type as Double. Huzzah! We got our Double out from inside the optional!

Not so fast. Try the same thing with size8:

 type(of: size8!)

Ack! The results pane says “Error,” and there’s a red band with a bunch of scary text about EXC_BAD_INSTRUCTION.

images/startingswift/optional-force-unwrap-crash.png

This is pretty bad: our code has crashed inside the playground. And the reason for that is something we need to remember: unwrapping nil values crashes our code! size8 is nil; we said to unwrap it with the ! operator—bang, we’re dead. Let’s delete that line so it doesn’t give us any more trouble!

Now we need to figure out what we’re going to do to not crash anymore. One option would be to always test optionals against nil, and only unwrap if they’re non-nil. That works, but it gets ugly. Nest a few if foo != nil blocks, and soon you’ve got what Swift developers call the “pyramid of doom” from all that indentation.

Unwrapping Optionals with if let

Fortunately, there’s a way out of this mess. We can combine let and if to create an expression that says “if you can assign this to a non-optional value, then give it the following name.” Here’s what that looks like:

 if​ ​let​ size = size7 {
  type(of:size)
 }
 
 if​ ​let​ size = size8 {
  type(of:size)
 }

Once we finish typing this, notice that the first if let block shows Double.Type for the type in the results pane, meaning that size is a normal Double inside the block and not an optional. But the second block of code doesn’t show anything, because its if let fails (because size8 is nil, so the contents inside the curly braces are not executed and size is not assigned).

The if let keyword gets used a lot, so it has a few tricks that will help us write more concise code. The first is that we can combine several if lets on a single line, comma-separated:

 if​ ​let​ size7 = size7, ​let​ size8 = size8 {
  type(of:size7)
  type(of:size8)
 }

There are two things to notice here. First, each assignment in an if let creates a variable name that’s only visible inside the scope of the curly braces. Often, it makes sense to just use the same name that a variable has outside the if let. So, in this case, if let size7 = size7 is not a meaningless tautology; instead, it looks at the right side (the optional size7) and says “if that’s not nil, create an unwrapped variable also called size7 inside the curly-brace scope.” At first it may look weird, but it’s a convention that comes easily to Swift programmers and is better than having to come up with different variable names for use only inside the if let block.

Second, there’s nothing in the results pane, because not all of the if let assignments succeeded. Since size8 is nil, we can’t unwrap it, and the second let fails.

One other trick we use a lot is testing a value that we’ve just unwrapped, as part of the if let. For example, what if we want to run some code on an optional Double only if it’s non-nil and its value is greater than some constant? We could use an if let followed by a if size7 > 100.0, but nesting ifs is going to give us that “pyramid of doom” we spoke of before. Instead, we can do this:

 if​ ​let​ size7 = size7, size7 > 100.0 {
  size7
 }

The logical expression after the comma on an if let allows us to perform logic with the unwrapped size7 Double while still on the if let line. This makes it clear that everything on the if let line has to pass for us to get into the curly-brace block.

It may seem like a lot of work to deal with optionals, but the concept ends up being powerful: we can use a single variable to both hold a value and to say “nothing to see here” if there isn’t a value. In some languages, we’d either have to use two variables for that, or a magical flag value that we just agree to treat as a “no value” value. And programming history has shown that approach can cause a lot of unexpected problems.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset