Optional Chaining

Version: Swift 5.5 Source: swift-book: Optional Chainingarrow-up-right Digest Date: January 24, 2022

Optional chaining is a process for querying and calling properties, methods, and subscripts on an optional that might currently be nil. Multiple queries can be chained together, and the entire chain fails gracefully if any link in the chain is nil.

Optional Chaining as an Alternative to Forced Unwrapping

  • 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 point (!) 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.

The next several code snippets demonstrate how optional chaining differs from forced unwrapping and enables you to check for success.

First, two classes called Person and Residence are defined:

If you create a new Person instance, its residence property is default initialized to nil, by virtue of being optional. In the code below, john has a residence property value of nil:

If you try to access the numberOfRooms property of this person’s residence, by placing an exclamation point after residence to force the unwrapping of its value, you trigger a runtime error, because there’s no residence value to unwrap:

Optional chaining provides an alternative way to access the value of numberOfRooms. To use optional chaining, use a question mark in place of the exclamation point:

This tells Swift to “chain” on the optional residence property and to retrieve the value of numberOfRooms if residence exists.

Because the attempt to access numberOfRooms has the potential to fail, the optional chaining attempt returns a value of type Int?, or “optional Int”.

The optional Int is accessed through optional binding to unwrap the integer and assign the non-optional value to the roomCount constant.

You can assign a Residence instance to john.residence, so that it no longer has a nil value:

john.residence now contains an actual Residence instance, rather than nil. If you try to access numberOfRooms with the same optional chaining as before, it will now return an Int? that contains the default numberOfRooms value of 1:

Defining Model Classes for Optional Chaining

You can use optional chaining with calls to properties, methods, and subscripts that are more than one level deep.

The code snippets below define four model classes for use in several subsequent examples, including examples of multilevel optional chaining.

These classes expand upon the Person and Residence model from above by adding a Room and Address class, with associated properties, methods, and subscripts.

The Person class is defined in the same way as before:

The Residence class is more complex than before. This time, the Residence class defines a variable property called rooms, which is initialized with an empty array of type [Room]:

As a shortcut to accessing its rooms array, this version of Residence provides a read-write subscript that provides access to the room at the requested index in the rooms array.

Finally, Residence defines an optional property called address, with a type of Address?. The Address class type for this property is defined below.

The Room class used for the rooms array is a simple class with one property called name, and an initializer to set that property to a suitable room name:

The final class in this model is called Address. This class has three optional properties of type String?. The first two properties, buildingName and buildingNumber, are alternative ways to identify a particular building as part of an address. The third property, street, is used to name the street for that address:

Accessing Properties Through Optional Chaining

Use the classes defined above to create a new Person instance, and try to access its numberOfRooms property as before:

Because john.residence is nil, this optional chaining call fails in the same way as before.

In this example, the attempt to set the address property of john.residence will fail, because john.residence is currently nil.

The assignment is part of the optional chaining, which means none of the code on the right-hand side of the = operator is evaluated.

In the previous example, it’s not easy to see that someAddress is never evaluated, because accessing a constant doesn’t have any side effects.

The listing below does the same assignment, but it uses a function to create the address. The function prints “Function was called” before returning a value, which lets you see whether the right-hand side of the = operator was evaluated.

You can tell that the createAddress() function isn’t called, because nothing is printed. (因为 john.residence 目前还是 nil

Calling Methods Through Optional Chaining

You can use optional chaining to call a method on an optional value, and to check whether that method call is successful. You can do this even if that method doesn’t define a return value.

The printNumberOfRooms() method on the Residence class prints the current value of numberOfRooms. Here’s how the method looks:

This method doesn’t specify a return type. However, functions and methods with no return type have an implicit return type of Void, as described in Functions Without Return Valuesarrow-up-right. This means that they return a value of (), or an empty tuple.

If you call this method on an optional value with optional chaining, the method’s return type will be Void?, not Void, because return values are always of an optional type when called through optional chaining. This enables you to use an if statement to check whether it was possible to call the printNumberOfRooms() method, even though the method doesn’t itself define a return value. Compare the return value from the printNumberOfRooms call against nil to see if the method call was successful:

The same is true if you attempt to set a property through optional chaining. Any attempt to set a property through optional chaining returns a value of type Void?, which enables you to compare against nil to see if the property was set successfully:

Accessing Subscripts Through Optional Chaining

You can use optional chaining to try to retrieve and set a value from a subscript on an optional value, and to check whether that subscript call is successful.

NOTE: When you access a subscript on an optional value through optional chaining, you place the question mark before the subscript’s brackets, not after. The optional chaining question mark always follows immediately after the part of the expression that’s optional.

The example below tries to retrieve the name of the first room in the rooms array of the john.residence property using the subscript defined on the Residence class. Because john.residence is currently nil, the subscript call fails:

The optional chaining question mark in this subscript call is placed immediately after john.residence, before the subscript brackets, because john.residence is the optional value on which optional chaining is being attempted.

Similarly, you can try to set a new value through a subscript with optional chaining:

This subscript setting attempt also fails, because residence is currently nil.

If you create and assign an actual Residence instance to john.residence, with one or more Room instances in its rooms array, you can use the Residence subscript to access the actual items in the rooms array through optional chaining:

Accessing Subscripts of Optional Type

If a subscript returns a value of optional type—such as the key subscript of Swift’s Dictionary type, place a question mark after the subscript’s closing bracket to chain on its optional return value:

The first two calls succeed, because the testScores dictionary contains keys for "Dave" and "Bev". The third call fails, because the testScores dictionary doesn’t contain a key for "Brian".

Linking Multiple Levels of Chaining

Multiple levels of optional chaining don’t add more levels of optionality to the returned value.

To put it another way:

  • If the type you are trying to retrieve isn’t optional, it will become optional because of the optional chaining.

  • If the type you are trying to retrieve is already optional, it will not become more optional because of the chaining.

Therefore:

  • If you try to retrieve an Int value through optional chaining, an Int? is always returned, no matter how many levels of chaining are used.

  • Similarly, if you try to retrieve an Int? value through optional chaining, an Int? is always returned, no matter how many levels of chaining are used.

The example below tries to access the street property of the address property of the residence property of john. There are two levels of optional chaining in use here, to chain through the residence and address properties, both of which are of optional type:

The value of john.residence currently contains a valid Residence instance. However, the value of john.residence.address is currently nil. Because of this, the call to john.residence?.address?.street fails.

If you set an actual Address instance as the value for john.residence.address, and set an actual value for the address’s street property, you can access the value of the street property through multilevel optional chaining:

Chaining on Methods with Optional Return Values

You can also use optional chaining to call a method that returns a value of optional type, and to chain on that method’s return value if needed.

The example below calls the Address class’s buildingIdentifier() method through optional chaining. This method returns a value of type String?. As described above, the ultimate return type of this method call after optional chaining is also String?:

If you want to perform further optional chaining on this method’s return value, place the optional chaining question mark after the method’s parentheses:

Last updated