895 lines
27 KiB
Markdown
895 lines
27 KiB
Markdown
# Optional Chaining
|
|
|
|
Access members of an optional value without unwrapping.
|
|
|
|
*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`.
|
|
|
|
> Note: Optional chaining in Swift is similar to messaging `nil` in Objective-C,
|
|
> but in a way that works for any type, and that can be checked for success or failure.
|
|
|
|
## 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`.
|
|
|
|
To reflect the fact that optional chaining can be called on a `nil` value,
|
|
the result of an optional chaining call is always an optional value,
|
|
even if the property, method, or subscript you are querying returns a non-optional value.
|
|
You can use this optional return value to check whether
|
|
the optional chaining call was successful
|
|
(the returned optional contains a value),
|
|
or didn't succeed due to a `nil` value in the chain
|
|
(the returned optional value is `nil`).
|
|
|
|
Specifically, the result of an optional chaining call
|
|
is of the same type as the expected return value, but wrapped in an optional.
|
|
A property that normally returns an `Int` will return an `Int?`
|
|
when accessed through optional chaining.
|
|
|
|
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:
|
|
|
|
```swift
|
|
class Person {
|
|
var residence: Residence?
|
|
}
|
|
|
|
class Residence {
|
|
var numberOfRooms = 1
|
|
}
|
|
```
|
|
|
|
<!--
|
|
- test: `optionalChainingIntro, optionalChainingIntroAssert`
|
|
|
|
```swifttest
|
|
-> class Person {
|
|
var residence: Residence?
|
|
}
|
|
|
|
-> class Residence {
|
|
var numberOfRooms = 1
|
|
}
|
|
```
|
|
-->
|
|
|
|
`Residence` instances have a single `Int` property called `numberOfRooms`,
|
|
with a default value of `1`.
|
|
`Person` instances have an optional `residence` property of type `Residence?`.
|
|
|
|
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`:
|
|
|
|
```swift
|
|
let john = Person()
|
|
```
|
|
|
|
<!--
|
|
- test: `optionalChainingIntro, optionalChainingIntroAssert`
|
|
|
|
```swifttest
|
|
-> let john = Person()
|
|
```
|
|
-->
|
|
|
|
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:
|
|
|
|
```swift
|
|
let roomCount = john.residence!.numberOfRooms
|
|
// this triggers a runtime error
|
|
```
|
|
|
|
<!--
|
|
- test: `optionalChainingIntroAssert`
|
|
|
|
```swifttest
|
|
-> let roomCount = john.residence!.numberOfRooms
|
|
xx assert
|
|
// this triggers a runtime error
|
|
```
|
|
-->
|
|
|
|
The code above succeeds when `john.residence` has a non-`nil` value
|
|
and will set `roomCount` to an `Int` value containing the appropriate number of rooms.
|
|
However, this code always triggers a runtime error when `residence` is `nil`,
|
|
as illustrated above.
|
|
|
|
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:
|
|
|
|
```swift
|
|
if let roomCount = 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."
|
|
```
|
|
|
|
<!--
|
|
- test: `optionalChainingIntro`
|
|
|
|
```swifttest
|
|
-> if let roomCount = john.residence?.numberOfRooms {
|
|
print("John's residence has \(roomCount) room(s).")
|
|
} else {
|
|
print("Unable to retrieve the number of rooms.")
|
|
}
|
|
<- Unable to retrieve the number of rooms.
|
|
```
|
|
-->
|
|
|
|
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`”.
|
|
When `residence` is `nil`, as in the example above,
|
|
this optional `Int` will also be `nil`,
|
|
to reflect the fact that it was not possible to access `numberOfRooms`.
|
|
The optional `Int` is accessed through optional binding
|
|
to unwrap the integer and assign the non-optional value
|
|
to the `roomCount` constant.
|
|
|
|
Note that this is true even though `numberOfRooms` is a non-optional `Int`.
|
|
The fact that it's queried through an optional chain
|
|
means that the call to `numberOfRooms`
|
|
will always return an `Int?` instead of an `Int`.
|
|
|
|
You can assign a `Residence` instance to `john.residence`,
|
|
so that it no longer has a `nil` value:
|
|
|
|
```swift
|
|
john.residence = Residence()
|
|
```
|
|
|
|
<!--
|
|
- test: `optionalChainingIntro`
|
|
|
|
```swifttest
|
|
-> john.residence = Residence()
|
|
```
|
|
-->
|
|
|
|
`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`:
|
|
|
|
```swift
|
|
if let roomCount = 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)."
|
|
```
|
|
|
|
<!--
|
|
- test: `optionalChainingIntro`
|
|
|
|
```swifttest
|
|
-> if let roomCount = john.residence?.numberOfRooms {
|
|
print("John's residence has \(roomCount) room(s).")
|
|
} else {
|
|
print("Unable to retrieve the number of rooms.")
|
|
}
|
|
<- John's residence has 1 room(s).
|
|
```
|
|
-->
|
|
|
|
## 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.
|
|
This enables you to drill down into subproperties
|
|
within complex models of interrelated types,
|
|
and to check whether it's possible to access
|
|
properties, methods, and subscripts on those subproperties.
|
|
|
|
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:
|
|
|
|
```swift
|
|
class Person {
|
|
var residence: Residence?
|
|
}
|
|
```
|
|
|
|
<!--
|
|
- test: `optionalChaining`
|
|
|
|
```swifttest
|
|
-> class Person {
|
|
var residence: Residence?
|
|
}
|
|
```
|
|
-->
|
|
|
|
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]`:
|
|
|
|
```swift
|
|
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?
|
|
}
|
|
```
|
|
|
|
<!--
|
|
- test: `optionalChaining`
|
|
|
|
```swifttest
|
|
-> 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?
|
|
}
|
|
```
|
|
-->
|
|
|
|
Because this version of `Residence` stores an array of `Room` instances,
|
|
its `numberOfRooms` property is implemented as a computed property,
|
|
not a stored property.
|
|
The computed `numberOfRooms` property simply returns
|
|
the value of the `count` property from the `rooms` array.
|
|
|
|
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.
|
|
|
|
This version of `Residence` also provides a method called `printNumberOfRooms`,
|
|
which simply prints the number of rooms in the residence.
|
|
|
|
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:
|
|
|
|
```swift
|
|
class Room {
|
|
let name: String
|
|
init(name: String) { self.name = name }
|
|
}
|
|
```
|
|
|
|
<!--
|
|
- test: `optionalChaining`
|
|
|
|
```swifttest
|
|
-> class Room {
|
|
let name: String
|
|
init(name: String) { self.name = 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:
|
|
|
|
```swift
|
|
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 if buildingName != nil {
|
|
return buildingName
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
<!--
|
|
- test: `optionalChaining`
|
|
|
|
```swifttest
|
|
-> class Address {
|
|
var buildingName: String?
|
|
var buildingNumber: String?
|
|
var street: String?
|
|
func buildingIdentifier() -> String? {
|
|
if let buildingNumber, let street {
|
|
return "\(buildingNumber) \(street)"
|
|
} else if buildingName != nil {
|
|
return buildingName
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
```
|
|
-->
|
|
|
|
The `Address` class also provides a method called `buildingIdentifier()`,
|
|
which has a return type of `String?`.
|
|
This method checks the properties of the address
|
|
and returns `buildingName` if it has a value,
|
|
or `buildingNumber` concatenated with `street` if both have values,
|
|
or `nil` otherwise.
|
|
|
|
## Accessing Properties Through Optional Chaining
|
|
|
|
As demonstrated in <doc:OptionalChaining#Optional-Chaining-as-an-Alternative-to-Forced-Unwrapping>,
|
|
you can use optional chaining to access a property on an optional value,
|
|
and to check if that property access is successful.
|
|
|
|
Use the classes defined above to create a new `Person` instance,
|
|
and try to access its `numberOfRooms` property as before:
|
|
|
|
```swift
|
|
let john = Person()
|
|
if let roomCount = 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."
|
|
```
|
|
|
|
<!--
|
|
- test: `optionalChaining`
|
|
|
|
```swifttest
|
|
-> let john = Person()
|
|
-> if let roomCount = john.residence?.numberOfRooms {
|
|
print("John's residence has \(roomCount) room(s).")
|
|
} else {
|
|
print("Unable to retrieve the number of rooms.")
|
|
}
|
|
<- Unable to retrieve the number of rooms.
|
|
```
|
|
-->
|
|
|
|
Because `john.residence` is `nil`,
|
|
this optional chaining call fails in the same way as before.
|
|
|
|
You can also attempt to set a property's value through optional chaining:
|
|
|
|
```swift
|
|
let someAddress = Address()
|
|
someAddress.buildingNumber = "29"
|
|
someAddress.street = "Acacia Road"
|
|
john.residence?.address = someAddress
|
|
```
|
|
|
|
<!--
|
|
- test: `optionalChaining`
|
|
|
|
```swifttest
|
|
-> let someAddress = Address()
|
|
-> someAddress.buildingNumber = "29"
|
|
-> someAddress.street = "Acacia Road"
|
|
-> john.residence?.address = someAddress
|
|
```
|
|
-->
|
|
|
|
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.
|
|
|
|
```swift
|
|
func createAddress() -> Address {
|
|
print("Function was called.")
|
|
|
|
let someAddress = Address()
|
|
someAddress.buildingNumber = "29"
|
|
someAddress.street = "Acacia Road"
|
|
|
|
return someAddress
|
|
}
|
|
john.residence?.address = createAddress()
|
|
```
|
|
|
|
<!--
|
|
- test: `optionalChaining`
|
|
|
|
```swifttest
|
|
-> func createAddress() -> Address {
|
|
print("Function was called.")
|
|
|
|
let someAddress = Address()
|
|
someAddress.buildingNumber = "29"
|
|
someAddress.street = "Acacia Road"
|
|
|
|
return someAddress
|
|
}
|
|
-> john.residence?.address = createAddress()
|
|
>> let _ = createAddress()
|
|
<< Function was called.
|
|
```
|
|
-->
|
|
|
|
You can tell that the `createAddress()` function isn't called,
|
|
because nothing is printed.
|
|
|
|
## 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:
|
|
|
|
```swift
|
|
func printNumberOfRooms() {
|
|
print("The number of rooms is \(numberOfRooms)")
|
|
}
|
|
```
|
|
|
|
<!--
|
|
- test: `optionalChainingCallouts`
|
|
|
|
```swifttest
|
|
-> func printNumberOfRooms() {
|
|
>> let numberOfRooms = 3
|
|
print("The number of rooms is \(numberOfRooms)")
|
|
}
|
|
```
|
|
-->
|
|
|
|
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 <doc:Functions#Functions-Without-Return-Values>.
|
|
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:
|
|
|
|
```swift
|
|
if john.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."
|
|
```
|
|
|
|
<!--
|
|
- test: `optionalChaining`
|
|
|
|
```swifttest
|
|
-> if john.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.")
|
|
}
|
|
<- It was not possible to print the number of rooms.
|
|
```
|
|
-->
|
|
|
|
The same is true if you attempt to set a property through optional chaining.
|
|
The example above in <doc:OptionalChaining#Accessing-Properties-Through-Optional-Chaining>
|
|
attempts to set an `address` value for `john.residence`,
|
|
even though the `residence` property is `nil`.
|
|
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:
|
|
|
|
```swift
|
|
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."
|
|
```
|
|
|
|
<!--
|
|
- test: `optionalChaining`
|
|
|
|
```swifttest
|
|
-> if (john.residence?.address = someAddress) != nil {
|
|
print("It was possible to set the address.")
|
|
} else {
|
|
print("It was not possible to set the address.")
|
|
}
|
|
<- It was not possible to set the address.
|
|
```
|
|
-->
|
|
|
|
## 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:
|
|
|
|
```swift
|
|
if let firstRoomName = 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."
|
|
```
|
|
|
|
<!--
|
|
- test: `optionalChaining`
|
|
|
|
```swifttest
|
|
-> if let firstRoomName = john.residence?[0].name {
|
|
print("The first room name is \(firstRoomName).")
|
|
} else {
|
|
print("Unable to retrieve the first room name.")
|
|
}
|
|
<- Unable to retrieve the first room name.
|
|
```
|
|
-->
|
|
|
|
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:
|
|
|
|
```swift
|
|
john.residence?[0] = Room(name: "Bathroom")
|
|
```
|
|
|
|
<!--
|
|
- test: `optionalChaining`
|
|
|
|
```swifttest
|
|
-> john.residence?[0] = Room(name: "Bathroom")
|
|
```
|
|
-->
|
|
|
|
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:
|
|
|
|
```swift
|
|
let johnsHouse = Residence()
|
|
johnsHouse.rooms.append(Room(name: "Living Room"))
|
|
johnsHouse.rooms.append(Room(name: "Kitchen"))
|
|
john.residence = johnsHouse
|
|
|
|
if let firstRoomName = 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."
|
|
```
|
|
|
|
<!--
|
|
- test: `optionalChaining`
|
|
|
|
```swifttest
|
|
-> let johnsHouse = Residence()
|
|
-> johnsHouse.rooms.append(Room(name: "Living Room"))
|
|
-> johnsHouse.rooms.append(Room(name: "Kitchen"))
|
|
-> john.residence = johnsHouse
|
|
|
|
-> if let firstRoomName = john.residence?[0].name {
|
|
print("The first room name is \(firstRoomName).")
|
|
} else {
|
|
print("Unable to retrieve the first room name.")
|
|
}
|
|
<- The first room name is Living Room.
|
|
```
|
|
-->
|
|
|
|
### 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:
|
|
|
|
```swift
|
|
var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
|
|
testScores["Dave"]?[0] = 91
|
|
testScores["Bev"]?[0] += 1
|
|
testScores["Brian"]?[0] = 72
|
|
// the "Dave" array is now [91, 82, 84] and the "Bev" array is now [80, 94, 81]
|
|
```
|
|
|
|
<!--
|
|
- test: `optionalChaining`
|
|
|
|
```swifttest
|
|
-> var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
|
|
-> testScores["Dave"]?[0] = 91
|
|
-> testScores["Bev"]?[0] += 1
|
|
-> testScores["Brian"]?[0] = 72
|
|
>> let dave = "Dave"
|
|
>> let bev = "Bev"
|
|
/> the \"Dave\" array is now [\(testScores[dave]![0]), \(testScores[dave]![1]), \(testScores[dave]![2])] and the \"Bev\" array is now [\(testScores[bev]![0]), \(testScores[bev]![1]), \(testScores[bev]![2])]
|
|
</ the "Dave" array is now [91, 82, 84] and the "Bev" array is now [80, 94, 81]
|
|
```
|
|
-->
|
|
|
|
The example above defines a dictionary called `testScores`,
|
|
which contains two key-value pairs that map a `String` key to an array of `Int` values.
|
|
The example uses optional chaining to set the first item in the `"Dave"` array to `91`;
|
|
to increment the first item in the `"Bev"` array by `1`;
|
|
and to try to set the first item in an array for a key of `"Brian"`.
|
|
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
|
|
|
|
You can link together multiple levels of optional chaining
|
|
to drill down to properties, methods, and subscripts deeper within a model.
|
|
However, 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:
|
|
|
|
```swift
|
|
if let johnsStreet = john.residence?.address?.street {
|
|
print("John's street name is \(johnsStreet).")
|
|
} else {
|
|
print("Unable to retrieve the address.")
|
|
}
|
|
// Prints "Unable to retrieve the address."
|
|
```
|
|
|
|
<!--
|
|
- test: `optionalChaining`
|
|
|
|
```swifttest
|
|
-> if let johnsStreet = john.residence?.address?.street {
|
|
print("John's street name is \(johnsStreet).")
|
|
} else {
|
|
print("Unable to retrieve the address.")
|
|
}
|
|
<- Unable to retrieve the address.
|
|
```
|
|
-->
|
|
|
|
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.
|
|
|
|
Note that in the example above,
|
|
you are trying to retrieve the value of the `street` property.
|
|
The type of this property is `String?`.
|
|
The return value of `john.residence?.address?.street` is therefore also `String?`,
|
|
even though two levels of optional chaining are applied in addition to
|
|
the underlying optional type of the property.
|
|
|
|
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:
|
|
|
|
```swift
|
|
let johnsAddress = Address()
|
|
johnsAddress.buildingName = "The Larches"
|
|
johnsAddress.street = "Laurel Street"
|
|
john.residence?.address = johnsAddress
|
|
|
|
if let johnsStreet = 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."
|
|
```
|
|
|
|
<!--
|
|
- test: `optionalChaining`
|
|
|
|
```swifttest
|
|
-> let johnsAddress = Address()
|
|
-> johnsAddress.buildingName = "The Larches"
|
|
-> johnsAddress.street = "Laurel Street"
|
|
-> john.residence?.address = johnsAddress
|
|
|
|
-> if let johnsStreet = john.residence?.address?.street {
|
|
print("John's street name is \(johnsStreet).")
|
|
} else {
|
|
print("Unable to retrieve the address.")
|
|
}
|
|
<- John's street name is Laurel Street.
|
|
```
|
|
-->
|
|
|
|
In this example,
|
|
the attempt to set the `address` property of `john.residence` will succeed,
|
|
because the value of `john.residence`
|
|
currently contains a valid `Residence` instance.
|
|
|
|
## Chaining on Methods with Optional Return Values
|
|
|
|
The previous example shows how to retrieve the value of
|
|
a property of optional type through optional chaining.
|
|
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?`:
|
|
|
|
```swift
|
|
if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
|
|
print("John's building identifier is \(buildingIdentifier).")
|
|
}
|
|
// Prints "John's building identifier is The Larches."
|
|
```
|
|
|
|
<!--
|
|
- test: `optionalChaining`
|
|
|
|
```swifttest
|
|
-> if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
|
|
print("John's building identifier is \(buildingIdentifier).")
|
|
}
|
|
<- John's building identifier is The Larches.
|
|
```
|
|
-->
|
|
|
|
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:
|
|
|
|
```swift
|
|
if let beginsWithThe =
|
|
john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {
|
|
if beginsWithThe {
|
|
print("John's building identifier begins with \"The\".")
|
|
} else {
|
|
print("John's building identifier doesn't begin with \"The\".")
|
|
}
|
|
}
|
|
// Prints "John's building identifier begins with "The"."
|
|
```
|
|
|
|
<!--
|
|
- test: `optionalChaining`
|
|
|
|
```swifttest
|
|
-> if let beginsWithThe =
|
|
john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {
|
|
if beginsWithThe {
|
|
print("John's building identifier begins with \"The\".")
|
|
} else {
|
|
print("John's building identifier doesn't begin with \"The\".")
|
|
}
|
|
}
|
|
<- John's building identifier begins with "The".
|
|
```
|
|
-->
|
|
|
|
> Note: In the example above,
|
|
> you place the optional chaining question mark *after* the parentheses,
|
|
> because the optional value you are chaining on is
|
|
> the `buildingIdentifier()` method's return value,
|
|
> and not the `buildingIdentifier()` method itself.
|
|
|
|
<!--
|
|
TODO: add an example of chaining on a property of optional function type.
|
|
This can then be tied in to a revised description of how
|
|
the sugar for optional protocol requirements works.
|
|
-->
|
|
|
|
<!--
|
|
This source file is part of the Swift.org open source project
|
|
|
|
Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
|
|
Licensed under Apache License v2.0 with Runtime Library Exception
|
|
|
|
See https://swift.org/LICENSE.txt for license information
|
|
See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
-->
|