Optional chain

An optional chained call is a method that can request and call properties, methods, and subscripts on an optional value whose current value may be nil. If the optional value has a value, the call succeeds; If the optional value is nil, the call returns nil. Multiple calls can be joined together to form a chain of calls, and if any of the nodes is nil, the whole chain of calls fails, that is, returns nil.

Pay attention to

Swift’s optional chain call is a bit like sending a message to nil in Objective-C, but Swift’s optional chain call can be applied to any type and can check if the call was successful.


Use optional chain calls instead of forced expansion

By placing a question mark (?) after the optional value of the property, method, or subscript that you want to call. , you can define an optional chain. This is a lot like putting an exclamation mark (!) after an optional value. To force its value to expand. The main difference is that an optional chained call will only fail when the optional value is null, whereas forced expansion will trigger a runtime error.

To reflect the fact that an optional chained call can be called on nil, the call returns an optional value regardless of whether its property, method, and subscript returns an optional value. You can use this return value to determine if your optional chained call was successful, if the call has a return value, and if it returns nil, it failed.

It is important to note here that the return result of the optional chained call has the same type as the original return result, but is wrapped as an optional value. For example, if an optional chain call is used to access a property, when the optional chain call succeeds, what if the property originally returned an Int? Type.

The following sections of code explain the difference between optional chained calls and forced expansion.

First, define two classes Person and Residence:

class Person {
    var residence: Residence?
}

class Residence {
    var numberOfRooms = 1
}
Copy the code

Residence has an attribute of type Int numberOfRooms, which defaults to 1. Person has an optional residence property of type residence? . If you create a new Person instance, its residence property will be initialized to nil because it is an optional type. In the following code, John has a residence property with a value of nil:

let john = Person(a)Copy the code

If you use an exclamation mark (!) Forcing the expansion to obtain the numberOfRooms value in John’s residence property triggers a runtime error because the residence has no value to expand:

letroomCount = john.residence! .numberOfRooms// This raises a runtime error
Copy the code

The above call succeeds when John. residence is non-nil and sets roomCount to the number of rooms of type Int. As mentioned above, this code fires a runtime error when residence is nil.

Optional chained calls provide another way to access numberOfRooms, using the question mark (?) To replace the exclamation mark (!). :

if letroomCount = john.residence? .numberOfRooms {print("John's residence has \(roomCount) room(s).")}else {
    print("Unable to retrieve the number of rooms.")}// Prints "Unable to retrieve the number of rooms."

Copy the code

After adding the question mark after residence, Swift accesses numberOfRooms if residence is not nil. Because it is possible to fail to access numberOfRooms, the optional chain call returns Int? Type, or “optional Int”. As shown in the example above, when residence is nil, the optional Int will be nil, indicating that numberOfRooms cannot be accessed. On successful access, the optional Int value is expanded through the optional binding and assigned to a roomCount constant of a non-optional type.

Note that this is true even if numberOfRooms is a non-optional Int. So just using the optional chain call means numberOfRooms is going to return an Int, right? Instead of Int. John. Residence can be assigned an instance of Residence so that it is no longer nil:

john.residence = Residence(a)Copy the code

John. residence now contains an actual residence instance instead of nil. If you try to access numberOfRooms using the previous optional chain call, it will now return an Int of 1, okay? Type value:

if letroomCount = john.residence? .numberOfRooms {print("John's residence has \(roomCount) room(s).")}else {
    print("Unable to retrieve the number of rooms.")}// Print "John's residence has 1 room(s)."
Copy the code

Define model classes for optional chained invocations

Multiple layers of properties, methods, and subscripts can be invoked by using optional chained calls. This allows you to drill down into the sub-attributes in a complex model and determine whether you can access the attributes, methods, and subscripts of the sub-attributes.

The following code defines four model classes. These examples include multiple layers of optional chain calls. For ease of illustration, the Room and Address classes have been added to the Person and Residence classes, along with their associated attributes, methods, and subscripts. The definition of the Person class remains essentially the same:

class Person {
    var residence: Residence?
}
Copy the code

Residence is more complex than before, adding a variable property called rooms, which is initialized as an empty array of type [Room] :

class Residence {
    var rooms = [Room] ()var numberOfRooms: Int {
        return rooms.count
    }
    subscript(i: Int) - >Room {
        get {
            return rooms[i]
        }
        set {
            rooms[i] = newValue
        }
    }
    func printNumberOfRooms(a) {
        print("The number of rooms is \(numberOfRooms)")}var address: Address?
}
Copy the code

Now that Residence has an array of Room instances, the numberOfRooms property is implemented as a computational rather than a stored property. The numberOfRooms property simply returns the value of the count property of the Rooms array.

Residence also provides a shortcut to access the Rooms array by providing read-write subscripts to access elements at specified locations in the Rooms array.

In addition, Residence provides the printNumberOfRooms method, which prints the value of numberOfRooms.

Finally, Residence defines an optional attribute address of type Address? . The definition of the Address class is explained below. The Room class is a simple class whose instances are stored in the Rooms array. This class contains only one property, name, and an initialization function that sets this property to the appropriate room name:

class Room {
    let name: String
    init(name: String) { self.name = name }
}
Copy the code

The last class is Address, and this class has three strings, right? An optional property of a type. The buildingName and buildingNumber properties represent the name and number of the building, respectively. The third property, street, represents the name of the street on which the building is located:

class Address {
    var buildingName: String?
    var buildingNumber: String?
    var street: String?
    func buildingIdentifier(a) -> String? {
        ifbuildingName ! =nil {
            return buildingName
        } else if let buildingNumber = buildingNumber, let street = street {
            return "\(buildingNumber) \(street)"
        } else {
            return nil}}}Copy the code

The Address class provides the buildingIdentifier() method, which returns String? . Return buildingName if it has a value. Alternatively, if both buildingNumber and Street have values, the concatenated string is returned. Otherwise, return nil.


Properties are accessed through optional chain calls

As described in using optional chain calls instead of force expansion, an optional chain call can access its properties on an optional value and determine whether the access was successful. Using the class defined earlier, create a Person instance and try accessing the numberOfRooms attribute as before:

let john = Person(a)if letroomCount = john.residence? .numberOfRooms {print("John's residence has \(roomCount) room(s).")}else {
    print("Unable to retrieve the number of rooms.")}// Prints "Unable to retrieve the number of rooms."
Copy the code

Because John. residence is nil, the optional chain call will still fail as before.

Property values can also be set via an optional chain call:

let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"john.residence? .address = someAddressCopy the code

In this example, setting the address property with John. residence will also fail because John. residence is currently nil.

The assignment in the code above is part of the optional chain call, which means that when the optional chain call fails, the code to the right of the equals sign is not executed. This is difficult to verify with the above code, because assigning a constant like this has no side effects. The following code does the same thing, but it uses a function to create an instance of Address, which is then returned for assignment. The Function prints “Function was called” before returning, which allows you to verify that the code to the right of the equals sign was executed.

func createAddress(a) -> Address {
    print("Function was called.")

    let someAddress = Address()
    someAddress.buildingNumber = "29"
    someAddress.street = "Acacia Road"

    returnsomeAddress } john.residence? .address = createAddress()Copy the code

With no printed message, you can see that the createAddress() function was not executed.


Methods are invoked via an optional chain call

An optional chained call can be used to call a method and determine whether the call succeeded, even if the method returns no value. The printNumberOfRooms() method in the Residence class prints the current numberOfRooms value as follows:

func printNumberOfRooms(a) {
    print("The number of rooms is \(numberOfRooms)")}Copy the code

This method returns no value. However, methods that do not return a value have an implicit return type Void, as described in the no-return function. This means that methods that do not return values will also return (), or empty tuples.

If the method was called via an optional chain call on an optional value, the return type of the method would be Void? , instead of Void, because the return values from the optional chain call are optional. This allows us to use the if statement to determine whether the printNumberOfRooms() method was successfully called, even if the method itself does not define a return value. Success of the call can be determined by determining whether the return value is nil:

ifjohn.residence? .printNumberOfRooms() ! =nil {
    print("It was possible to print the number of rooms.")}else {
    print("It was not possible to print the number of rooms.")}// Print "It was not possible to print the number of rooms."
Copy the code

Again, you can determine whether assigning a value to an attribute through an optional chain call was successful. In the example above of accessing a property through an optional chained call, we tried to assign the address property in John.residence even if residence was nil. Assigning to a property via an optional chain call returns Void? , we can tell if the assignment is successful by checking whether the return value is nil:

if(john.residence? .address = someAddress) ! =nil {
    print("It was possible to set the address.")}else {
    print("It was not possible to set the address.")}// Print "It was not possible to set the address."

Copy the code

Access the following table via an optional chained call

With optional chain calls, we can access subscripts on an optional value and determine whether the subscript call was successful.

Pay attention to

When accessing the subscript of an optional value through an optional chained call, place the question mark before the square brackets of the subscript instead of after it. The question mark for optional chained calls generally follows the optional expression directly.

The following example uses subscript to access the name of the first room in the rooms array of the residence instance stored by the John. residence property, and the subscript call fails because John. residence is nil:

if letfirstRoomName = john.residence? [0].name {
    print("The first room name is \(firstRoomName).")}else {
    print("Unable to retrieve the first room name.")}// Print "Unable to retrieve the first room name."
Copy the code

In this example, the question mark is placed directly after John. residence and before the square brackets because John. residence is optional. Similarly, assignment can be made with an optional chain call via subscript:

john.residence? [0] = Room(name: "Bathroom")
Copy the code

This assignment will also fail because residence is currently nil. If you create a Residence instance, add Room instances to its rooms array, and then assign the Residence instance to John. Residence, you can access elements in the array via optional chains and subscripts:

let johnsHouse = Residence()
johnsHouse.rooms.append(Room(name: "Living Room"))
johnsHouse.rooms.append(Room(name: "Kitchen"))
john.residence = johnsHouse

if letfirstRoomName = john.residence? [0].name {
    print("The first room name is \(firstRoomName).")}else {
    print("Unable to retrieve the first room name.")}// Print "The first room name is Living room."
Copy the code
Access the subscripts of optional types

If the subscript returns an optional type value, such as the subscript of a dictionary-type key in Swift, an optional chain call can be made on its optional return value by placing a question mark after the closing parentheses of the subscript:

var testScores = ["Dave": [86.82.84]."Bev": [79.94.81]]
testScores["Dave"]? [0] = 91
testScores["Bev"]? [0] + =1
testScores["Brian"]? [0] = 72
// "Dave" array is now [91, 82, 84], "Bev" array is now [80, 94, 81]
Copy the code

The above example defines a testScores array that contains two key-value pairs that map the String key to an array of Int values. This example uses an optional chain call to set the first element in the “Dave” array to 91, the first element in the “Bev” array to +1, and then tries to set the first element in the “Brian” array to 72. The first two calls succeeded because the testScores dictionary contains the keys “Dave” and “Bev”. But the testScores dictionary does not have the key “Brian”, so the third call fails.


Connect multiple layers of optional chain calls

You can access properties, methods, and subscripts at a deeper model level by concatenating multiple optional chained calls. However, a multi-tier optional chain call does not increase the optional level of the return value.

That is: if the value you accessed is not optional, the optional chain call will return the optional value. If the value you are accessing is optional, the optional chain call does not make the optional return value “more optional”.

So: calling an Int via an optional chain call returns Int? , no matter how many layers of optional chain calls are used.

Similarly, Int? Is accessed via an optional chain call. Value, will still return Int, okay? Value does not return Int?? .

The following example attempts to access the street property in the Address property in the residence property in John. Here two layers of optional chained calls are used, with residence and address being optional values:

if letjohnsStreet = john.residence? .address? .street {print("John's street name is \(johnsStreet).")}else {
    print("Unable to retrieve the address.")}// Prints "Unable to retrieve the address."
Copy the code

John. residence now contains a valid residence instance. However, the value of John.residence. Address is currently nil. Therefore, call John.residence? .address? .Street will fail.

Note that in the above example, street has the property String, right? . john.residence? .address? .street returns String? , even though two layers of optional chain calls have been used. If we assign an address instance to John.residence. Address and set a valid value for the street property in address, we can access the street property via an optional chained call:

let johnsAddress = Address()
johnsAddress.buildingName = "The Larches"
johnsAddress.street = "Laurel Street"john.residence? .address = johnsAddressif letjohnsStreet = john.residence? .address? .street {print("John's street name is \(johnsStreet).")}else {
    print("Unable to retrieve the address.")}// Print "John's Street name is Laurel Street."
Copy the code

In the above example, assignment to the Address attribute of John. residence will succeed because john.residence contains a valid Address instance.


Make an optional chain call on the optional return value of a method

The above example shows how to get the value of an attribute on an optional value by an optional chain call. We can also call a method via an optional chain call on an optional value, and can continue to do the optional chain call on the optional return value of the method as needed.

In the following example, the buildingIdentifier() method of Address is called via an optional chain call. This method returns String, okay? Type. As mentioned above, if you call this method with an optional chained call, the return value will still be String, okay? Type:

if letbuildingIdentifier = john.residence? .address? .buildingIdentifier() {print("John's building identifier is \(buildingIdentifier).")}// Print "John's building identifier is The Larches."
Copy the code

To make an optional chaining call on the return value of the method, place a question mark after the method’s parentheses:

if letbeginsWithThe = john.residence? .address? .buildingIdentifier()? .hasPrefix("The") {
        if beginsWithThe {
            print("John's building identifier begins with \"The\".")}else {
            print("John's building identifier does not begin with \"The\".")}}// Print "John's building identifier begins with "The".
Copy the code

Note that in the above example, the question mark is placed after the method parentheses because you are making an optional chain call on the optional return value of the buildingIdentifier() method, not the buildingIdentifier() method itself.