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

A few times so far we’ve seen our log messages include the term “optional,” a behavior we’ve put off explaining until now. 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 6s"​: 138.1,
 "iPhone 6s Plus"​ : 158.1,
 "iPad Air 2"​ : 240.0]

Looking at this, we can see that sizeInMm["iPhone 6s"] 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 7"], 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 dynamicType of the value we get back:

 let​ size6 = sizeInMm[​"iPhone 6s"​]
 size6.​dynamicType

In the results pane, this shows size6 as 138.1 and the dynamicType as Optional<Double>.Type.

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

 let​ size7 = sizeInMm[​"iPhone 7"​]
 size7.​dynamicType

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 size6:

 size6!.​dynamicType

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

Not so fast. Try the same thing with size7:

 size7!.​dynamicType

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! size7 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 = size6 {
  size.​dynamicType
 }
 
 if​ ​let​ size = size7 {
  size.​dynamicType
 }

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 size7 is nil, so 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​ size6 = size6, size7 = size7 {
  size6.​dynamicType
  size7.​dynamicType
 }

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 size6 = size6 is not a meaningless tautology; instead, it looks at the right side (the optional size6) and says “if that’s not nil, create an unwrapped variable also called size6 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 size7 is nil, we can’t unwrap it, and the if 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 size6 > 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​ size6 = size6 ​where​ size6 > 100.0 {
  size6
 }

The where clause on an if let allows us to perform logic with the unwrapped size6 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
18.216.124.145