Optional Chaining + Optional Binding - Xcode 11 & Swift 5

Optional Chaining + Optional Binding - Xcode 11 & Swift 5

"Optional chaining is a process for querying and calling properties, methods, and subscripts on an optional that might currently be nil. If the optional contains a value, the property, method, or subscript call succeeds; if the optional is nil, the property, method, or subscript call returns nil. Multiple queries can be chained together, and the entire chain fails gracefully if any link in the chain is nil."

When I first began coding in Swift, optionals were a thing to be handled with force. My code was littered with question marks and infested with exclamation marks. Eventually the tech debt caught up to me and I learned to embrace optionals as a tool and not a hindrance. Optionals are manageable, not overwhelming. In this article we will discuss using optional binding to unwrap optional chains.

To follow along, you will need to create a fresh playground. Once created, open the identity tab and confirm that Playground Settings are set to macOS. This solves a common error where the playground never compiles or runs. Strange bug, but easy fix. You can open the tab by clicking the top right button on the tool bar. If your tool bar is missing, just hit Option-Command-T, or go to View -> Show Toolbar.

No alt text provided for this image

You specify optional chaining by placing a question mark after the optional value on which you wish to call a property, method or subscript if the optional is non-nil. This is very similar to placing an exclamation mark after an optional value to force the unwrapping of its value. The main difference is that optional chaining fails gracefully when the optional is nil, whereas forced unwrapping triggers a runtime error when the optional is nil.

import Foundation

class beverage {
    var flavor: String?
}

class vendingMachine {
    var beverage: beverage?
}

let purpleVendingMachine = vendingMachine()

In our experimentation, we will imagine a vending machine which sells a beverage that has a flavor. Before we address optional chaining, let's first see what our purpleVendingMachine contains.

print(purpleVendingMachine.beverage) //nil

print(purpleVendingMachine.beverage?.flavor) //nil

It returns nil, but it returns nil without crashing. If you look at the second line you can see optional chaining. If you were to try the same line but with (!) instead of (?), the playground would have a fatal error; go ahead and try it.

print(purpleVendingMachine.beverage!.flavor)

If at this point if classes feel unfamiliar or daunting, you may want to check out the material in the following link which discusses classes and structures. For the purposes of this article it is not entirely necessary that you understand how to change the values of the purpleVendingMachine, but it is critical to know in general. Changing the content of the vending machine in the following way will not work, although it might be tempting to try if unfamiliar with classes.

//this will not change the value
purpleVendingMachine.beverage?.flavor = "apple juice"
print(purpleVendingMachine.beverage?.flavor) . //nil

Create a beverage to put into our vending machine, and then print the output after it has been assigned.

import Foundation


class beverage {
    var flavor: String?
}


class vendingMachine {
    var beverage: beverage?
}


let purpleVendingMachine = vendingMachine()


let appleJuice = beverage()
appleJuice.flavor = "Apple Juice"
purpleVendingMachine.beverage = appleJuice


print(purpleVendingMachine.beverage?.flavor) //Optional ("Apple Juice")

You will notice that the console outputs Optional("Apple Juice") instead of just Apple Juice. Changing the (?) to a (!) won't solve this issue either. Now we will properly unwrap the flavor with optional binding. We use optional binding to find out whether an optional contains a value, and if so, to make that value available as a temporary constant or variable.

You can use both constants and variables with optional binding.

Optional binding can be used with if and while statements to check for a value inside an optional, and to extract that value into a constant or variable, as part of a single action.

if let purpleFlavor = purpleVendingMachine.beverage?.flavor {
    print("This vending machine sells \(purpleFlavor) flavored beverages")
    //This vending machine sells Apple Juice flavored beverages
} else {
    restock()
    print("This vending machine will be restocked soon")

}

Using optional binding with optional chaining is a great way to handle nil values in our apps. This format safely unwraps the purpleVendingMachine beverage, and even allows for you to do something when the value is nil. In our example, I added restock() which would conceivably restock the machine every time the optional binding fails. Generally, guard statements will do a similar thing. I personally prefer guard statements, so here is an example with the same result, but using guard. In this example, we will even set up a function that gets the flavor of an input vending machine.

func getTheFlavor(inputVM: vendingMachine) -> String{

    guard let guardThePurpleFlavor = inputVM.beverage?.flavor else {
        restock()
        return "This vending machine sells no flavored beverages"
    }
    return "This vending machine sells \(guardThePurpleFlavor)     flavored beverages"
}


print(getTheFlavor(inputVM: purpleVendingMachine))
//This vending machine sells Apple Juice flavored beverages

At this point, let's create two more vending machines and a new beverage so we can see how our code handles nil values with optional chaining.

import Foundation


class beverage {
    var flavor: String?
}


class vendingMachine {
    var beverage: beverage?
}


func restock() { }


//purpleVM
let purpleVendingMachine = vendingMachine()
let blueVendingMachine = vendingMachine()
let grayVendingMachine = vendingMachine()


//apple juice
let appleJuice = beverage()
appleJuice.flavor = "Apple Juice"
purpleVendingMachine.beverage = appleJuice


//orange juice
let orangeJuice = beverage()
orangeJuice.flavor = "Orange Juice"
blueVendingMachine.beverage = orangeJuice


func getTheFlavor(inputVM: vendingMachine) -> String{
    guard let guardThePurpleFlavor = inputVM.beverage?.flavor else {
        restock()
        return "This vending machine sells no flavored beverages"
    }
    return "This vending machine sells \(guardThePurpleFlavor) flavored beverages"
}


print(getTheFlavor(inputVM: purpleVendingMachine)) //Apple Juice
print(getTheFlavor(inputVM: blueVendingMachine)) //Orange Juice
print(getTheFlavor(inputVM: grayVendingMachine)) /no flavored beverages

As you can see, our code now can accept different types of flavors or no flavor at all without crashing, and it is all thanks to the (?) after beverage. You can continue chaining deeper, Swift will check each optional from left to right. The important takeaway is that you can tell the code to only continue if the value is not nil else do something different. With this in mind, you should be able to create intricate systems. Now we will take a look at a more messy example with NSDictionaries.

import Foundation



var purpleBeverages = ["Juice": ["Orange Juice", "Apple Juice"], "Sodas" : ["Root Beer"]]

var vendingMachines = ["Purple": purpleBeverages]


func getTheDictionaryFlavor(inputVM: String, typeOfBeverage: String, positionOfBeverageType: Int) -> String {

    if positionOfBeverageType < vendingMachines["\(inputVM)"]?["\(typeOfBeverage)"]?.count ?? 0 {

        guard let guardTheFlavor = vendingMachines["\(inputVM)"]?["\(typeOfBeverage)"]?[positionOfBeverageType]  else {

            return "There was an error finding the flavor"

        }

        return "The flavor requested is \(guardTheFlavor)"

    } else {

        return "There was an error finding the flavor"

    }

}

print(getTheDictionaryFlavor(inputVM: "Purple", typeOfBeverage: "Juice", positionOfBeverageType: 0)) //The flavor requested is Orange Juice


print(getTheDictionaryFlavor(inputVM: "Purple", typeOfBeverage: "Juice", positionOfBeverageType: 2)) //There was an error finding the flavor

print(getTheDictionaryFlavor(inputVM: "Nurple", typeOfBeverage: "Juice", positionOfBeverageType: 0)) //There was an error finding the flavor

In this example we have a chain of more than one optional, and we use a conditional statement before optional binding. The conditional if positionOfBeverageType checks to make sure that there are enough values in the array, else it returns an error. This function may look more complicated, but apart from the nil coalescing operator, it only contains elements of what we have already discussed.

A nil coalescing operator is an incredibly useful operator (??) it is used to provide a default for your optional if it returns nil. In our example, without the nil coalescing operator, we would have an error if the function checked purple beverages for "Nurple" and found nil. The conditional statement which checks if our input is less than the nil array.count would crash if we force unwrapped the optional. Notably, you could simply use a guard statement to check if the array exists before proceeding, but this is faster. When coding your app, you will need to consider when to use nil coalescing operators and when to use conditionals and methods to alter your data and unwrap optionals. Be aware that if you abuse the nil coalescing operator you will eventually accumulate tech debt.

At this point, we have covered using conditionals, the nil coalescing operator, and optional binding to successfully unwrap and handle optional chains. We covered how they work with classes and touched on how they might work in an inelegant scenario.

If you have any questions, feel free to send me a message or email [email protected]

要查看或添加评论,请登录

Ean Krenzin-Blank的更多文章

社区洞察

其他会员也浏览了