The original

Optional chaining is the process of querying and calling properties, methods, and subscripts at an optional value that may now be nil. If the optional type contains a value, attribute, method, or subscript, the call succeeded; If optional is nil, property, method, or subscript calls return nil. Multiple queries can be chained simultaneously, and if any link in the chain is nil the whole chain fails gracefully.

The optional chain in Swift is like nil message passing in Objective-C, but in a way that any type can check for success or failure.

Optional Chaining as an Alternative to Forced Unwrapping

If the optional type makes it non-nil you want to call the optional value of the property, method or subscript to prevent a question mark after the optional chain. This is similar to forcing an exclamation mark after an optional value to unpack its value. The difference is that the optional chain gracefully fails when the optional value is nil, and forced unpacking fires a runtime error when the optional type makes nil.

To reflect the case where an optional chain can be called on a nil value, the result of the call to an optional chain is usually an optional value, even if the property, method, or subscript you query returns a value of a non-optional type. You can use this optional return value to check if the optional chain call succeeded (the optional type returned contains a value) or if a nil value in the chain did not succeed (the optional value returned is nil).

In particular, the result of calling an optional chain is the same type as the expected return value, but wrapped in an optional type. A property that normally returns an Int returns an Int when accessed via an optional chain, okay? .

The following code blocks explain the difference between optional chains and strong unpacking and allow you to check for success.

First we define two classes named Person and Residence:

class Person {
    var residence: Residence?
}

class Residence {
    var numberOfRooms = 1
}Copy the code

Residece instances have a separate Int property called numberOfRooms, which has a default value of 1.Person instances have a Residence? Type with the optional attribute residence.

If you create a new Person instance, its residence property is initialized to nil by default because it is optional. In the following code, John has a residence property with a value of nil:

let john = Person()Copy the code

If you try to access the numberOfRooms property of this person’s residence, forcibly unpack its value by placing an exclamation mark after the residence, triggering a runtime error because there is no residence value to unpack:

letroomCount = john.residence! .numberOfRooms // this triggers a runtime errorCopy the code

The above code succeeds if John.residence has a non-nil value and sets roomCount to an Int containing the appropriate number of rooms. However, this code usually fires a runtime error when residence is nil, as explained above.

Optional chains provide another way to access the value of numberOfRooms. To use optional chains, use a question mark instead of an 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

This tells Swift to link to the optional residence property and get the value of numberOfRooms if the residence exists.

Because attempts to access numberOfRooms may potentially fail, the optional chain attempt returns an Int? Type, or “Optional Int”. When residence is nil, as in the example above, the optional Int will also be nil to reflect the fact that it is impossible for him to access numberOfRooms. Access the optional Int via the optional binding unpack integer and assign a non-optional value to the roomCount variable.

Note that even if numberOfRooms is a non-optional Int, it’s ok. And he’s actually doing an optional type query which means that calls to numberOfRooms usually return Int instead of Int, right? .

You can assign a Residence instance to John. Residence, so there will be no nil value:

john.residence = Residence()Copy the code

John.residence now contains an actual residence instance instead of nil. If you try to access numberOfRooms with the same optional chain as before, it now returns an Int of numberOfRooms with the default bit 1, okay? :

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

Defining Model Classes for Optional Chaining

We can use optional chains by calling more than one level of properties, methods, and subscripts. This allows you to drill down to subproperties in a complex interconnected type model and check if it is possible to access properties, methods, and subscripts in those subproperties.

The following code snippet defines four model classes for use in multi-subsequence examples, including examples of multi-layer optional chains. These classes extend the Person and Residence models above by adding a Room and Address class, with associated attributes, methods, and subscripts.

The Person class works the same way as defined above:

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

The Residence analogy is more complicated. This time, the Residence class defines a mutable property called rooms, initialized with 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() {
        print("The number of rooms is \(numberOfRooms)")
    }
    var address: Address?
}Copy the code

Because this version of Residence stores an array of Room instances, its numberOfRooms property is implemented as a calculated property, not a storage property. The numberOfRooms property simply returns the count property of the Rooms array.

As a quick access to its Rooms array, this version of Residence provides a read and write subscript to provide access to the requested index location in the Rooms array.

This version of Residence also provides a method called printNumberOfRooms that simply prints the number of rooms in a Residence.

Finally, Residence defines an optional property called address of type Address? .

The Address class type for this attribute is defined below.

The Room class for the Rooms array is a simple class with a property named Name and an initializer that sets the property to the appropriate Room name.

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

The final class of the partition model is called Address. This class has three strings, right? An optional property of a type. The first two properties, buildingName and buildingNumber, are two ways to identify a particular building in an Address. The third attribute, street, names the street of address:

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

The Address class also provides a method called buildingIdentifier () that has a String? The return type of. This method checks the property of address and returns buildingName if it has a value, or concatenates street’s buildingName if both have values, nil otherwise.

Accessing attributes Through Optional Chaining

As explained in Optional Chaining as an Alternative to Forced Unwrapping, you can use Optional chain access to access a property in the Optional value and check if the property access is successful.

Use the class definition above to create a new Person instance and try to access its numberOfRooms attribute as before:

let john = Person()
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 fails to be called in the same way as before.

You can also try setting the value of a property via an optional chain:

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

In this example, trying to set the John. residence property of address will fail because John. residence is now nil.

Assignment is part of the optional chain, meaning there is no code to execute on the = right. In the example above, it is hard to tell that someAddress was not executed because accessing a constant has no other representation. The following list does the same assignment, but uses a function to create address. Before returning the value, the Function prints “Function was called” to let you see if the = right-hand side is executed.

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

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

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

You can see that the createAddress () function is not called because nothing is printed.

Calling Methods Through Optional Chaining

You can use optional chains to call a method on an optional value and check if that method was called successfully. You can do this even if the method does not define a return value.

The printNumberOfRooms () method of the Residence class prints the current value of numberOfRooms, and here’s what the method looks like:

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

This method does not have a top return type. However, Functions and methods that do not Return a value have a potential Void Return type, as described in Functions Without Return Values. This means they return a value of (), or an empty primitive.

If you call this method on an optional value with an optional chain, the return value of the method will be Void? Void. Because the return value is usually an optional type when called through an optional chain. This allows you to use the if statement to check whether it is possible to call the printNumberOfRooms () method, even if the method itself does not define a return value. Compare the value returned from the printNumberOfRooms call to nil to see if the method was called successfully:

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.")
}
// Prints "It was not possible to print the number of rooms."Copy the code

This works if you try to set a property via an optional chain. The above example in Accessing Properties Through Optional Chaining tries to set an address value for John. residence, even if the residence attribute is nil. Any attempt to set a property via an optional chain returns a Void? Type that allows you to compare to nil to see if the property was set successfully:

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

Accessing Subscripts Through Optional Chaining

You can use optional chains to try to get and set a value from the subscript of an optional value and check if the following table call succeeded.

When you access a subscript on an optional value via an optional chain, place a question mark before the square brackets of the underlying value instead of after it. The question mark for an optional chain usually follows a partial alternative expression

The following example tries to get the name of the first room in the rooms array of the John. Residence property using the subscript defined in the Residence class. Because John. residence is now nil, the subscript call fails:

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

The optional chain question mark on the subscript call is placed after John. residence and before the subscript square brackets because John. residence is an optional value on the attempted optional chain.

Similarly, you can try setting a new value with an optional chain by subscript:

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

The subscript attempt to set the value also fails because residence is now nil.

If you create and assign an actual residence instance to John. residence and use one or more Room instances in its Rooms array, you can optionally access the actual objects in the Rooms array with the residence subscript:

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.")
}
// Prints "The first room name is Living Room."Copy the code

(Accessing Subscripts of Optional Type)

If a subscript returns a value of an optional type — for example, swift’s dictionary key subscript — place a question mark after the closing square bracket of the subscript to link its optional return value:

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

The above example defines a dictionary called testScores that contains two key-value pairs mapping an array of Int values with a String key. The example uses an optional chain to set the first object of the “Dave” array to 91; The first object in “Bev” is incremented by 1; Try to set the first object in the array corresponding to “Brian” ‘s key. The first two calls succeed because the testScores dictionary contains the keys for “Dave” and “Bev.” The third call fails because the testScores dictionary does not contain a key for “Brian”.


Linking Multiple Levels of Chaining

Multiple layers of an optional chain can be linked together to get attributes, methods, and subscripts down in a model. However, the multiple levels of the optional chain do not add more optional levels to the return value.

In other words:

  • If the type you are trying to get is not optional, it becomes optional due to optional chains.
  • If the type you’re trying to get is an optional type, it doesn’t become more optional because of the chain.

So:

  • If you try to get an Int from an optional chain, it usually returns an Int, right? No matter how many layers of chain are used.
  • Similarly, if you try to get an Int from an optional chain, right? Value, usually returning Int? “, but how many layers of chain.
  • The following example attempts to access the street property of the Address property of John’s residence property. Here two layers of optional chains are used, linking the residence and address attributes, both of which are optional types:

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

The value of John.residence now contains a valid instance of residence. But the value of John.residence. Address is now nil. Therefore, call John.residence? address? Street failure.

Notice in the example above that you are trying to get the value of the street property. This property is of type String, right? . So johm. Residence? .address? .street returns a String? Even though two layers of optional chains are used in addition to the underlying optional types of attributes.

If you set the real Address instance to the value of John.residence. Address and set the real value to the street property of Address, the value of the street property can be accessed through multiple levels of optional links:

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.")
}
// Prints "John's street name is Laurel Street."Copy the code

In this example, the attempt to set the address attribute of John. residence succeeded because the value of John. residence currently contains a valid residence instance.

Chaining On Methods with Optional Return Values

The example above shows how to get the value of an optional type attribute through an optional link. You can also use an optional link to call a method that returns an optional type of value, chained to the method’s return value if desired.

The following example calls the buildingIdentifier () method of the Address class via an optional link. This method returns a String, okay? Type. As described above, the final return value of calling this method after an optional link is also String, right? :

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

If you want to perform more alternative links on the return value of this method, prevent the alternative link question mark after the parentheses of the method:

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\".")
    }
}
// Prints "John's building identifier begins with "The"."Copy the code

In the example above, place an optional link question mark after the parentheses, because the optional value of your link is the return value of the buildingIdentifier () method, not the buildingIdentifier () method itself.


If the array is optional, the array name is followed by? . If the array subscript returns an optional type, square brackets followed by? .


If the dictionary is optional, what is the dictionary name followed by? . Dictionary subscripts return optional types, square brackets followed by? .


The optional type is an enumeration