Swift optional chain
An optional chain is a process that can request and invoke properties, methods, and subscripts, and the target for a request or call may be nil
.
The optional chain returns two values:
If the target has a value, the call succeeds and returns the value
If the target is
nil
the call will returnnil
Multiple requests or calls can be linked into a chain if any node is nil
will cause the whole chain to fail.
Optional chain can replace forced parsing
An optional chain can be defined by placing a question mark (?) after the optional value of a property, method, or subscript.
Optional chain’?’ |
Exclamation point (!) forces the expansion of methods, attributes, and optional chains of subscript scripts |
---|---|
? Call methods, properties, and subscript scripts after placing optional values |
! Methods, properties, and subscript scripts are called after placing the optional value to force the expansion of the value |
Output more friendly error messages when optional as nil |
Force expand execution error when nil is optional |
Use the exclamation point (!) Optional chain instance
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}
let john = Person()
//Will cause runtime errors
let roomCount = john.residence!.numberOfRooms
The output of the above program execution is as follows:
fatal error: unexpectedly found nil while unwrapping an Optional value
Want to use the exclamation point (!) to force parsing to get this person residence
attribute numberOfRooms
property value, a run-time error will be raised because there is no residence
value.
Use the question mark (?) Optional chain instance
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}
let john = Person()
// Link optional residence? Property, if residence exists, retrieve the value of numberOfRooms
if let roomCount = john.residence?.numberOfRooms {
print("John's room number is \(roomCount)。")
} else {
print("Unable to view room number")
}
The output of the above program execution is as follows:
Unable to view room number
Because of this attempt to get numberOfRooms
may fail, and the optionalchain will return Int?
type value, or “optional Int”. When residence
when empty (example above), select Int
will be empty, so it will be inaccessible numberOfRooms
the situation.
It is important to note that even if numberOfRooms
yes or no option Int(Int?)
. This is also true. As long as the request through the optionalchain means that in the end numberOfRooms
always return a Int?
instead of Int
.
Define a model class for an optional chain
You can use optional chains to call properties, methods, and subscript scripts at multiple levels. This allows you to take advantage of the complexmodel between them to obtain lower-level properties and check whether such underlying properties can be successfully obtained.
Example
Four model classes are defined, including multi-layer optional chains:
class Person {
var residence: Residence?
}
// Defined a variable 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 {
return rooms[i]
}
func printNumberOfRooms() {
print("Room number is \(numberOfRooms)")
}
var address: Address?
}
// Room Define a name attribute and an initializer for setting the room name
class Room {
let name: String
init(name: String) { self.name = name }
}
// The final class in the model is called Address
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if (buildingName != nil) {
return buildingName
} else if (buildingNumber != nil) {
return buildingNumber
} else {
return nil
}
}
}
Call a method through an optional chain
You can use the optional chain to call the method of the optional value and check whether the method call is successful. Even if this method does not return a value, you can still use an optional chain to achieve this.
class Person {
var residence: Residence?
}
// Defined a variable 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 {
return rooms[i]
}
func printNumberOfRooms() {
print("Room number is \(numberOfRooms)")
}
var address: Address?
}
// Room defines a name attribute and an initializer that sets the room name
class Room {
let name: String
init(name: String) { self.name = name }
}
// The final class in the model is called Address
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if (buildingName != nil) {
return buildingName
} else if (buildingNumber != nil) {
return buildingNumber
} else {
return nil
}
}
}
let john = Person()
if ((john.residence?.printNumberOfRooms()) != nil) {
print("Output room number")
} else {
print("Unable to output room number")
}
The output of the above program execution is as follows:
Unable to output room number
Use the if statement to check whether the call can be successfully called printNumberOfRooms
method: if the method is successfully called through the optional chain printNumberOfRooms
the implicit return value of willbe Void
if it is not successful, it returns nil
.
Invoke the subscript script using the optional chain
You can use the optional chain to try to get a value from the subscript script and check whether the call to the subscript script is successful. However, you cannot set the subscript script through the optional chain.
Example 1
class Person {
var residence: Residence?
}
// Defined a variable 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 {
return rooms[i]
}
func printNumberOfRooms() {
print("Room number is\(numberOfRooms)")
}
var address: Address?
}
// Room defines a name attribute and an initializer that sets the room name
class Room {
let name: String
init(name: String) { self.name = name }
}
// The final class in the model is called Address
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if (buildingName != nil) {
return buildingName
} else if (buildingNumber != nil) {
return buildingNumber
} else {
return nil
}
}
}
let john = Person()
if let firstRoomName = john.residence?[0].name {
print("First room name \(firstRoomName).")
} else {
print("Unable to retrieve room")
}
The output of the above program execution is as follows:
Unable to retrieve room
The question mark of the optional chain in the subscript call comes directlyafter the john.residence and before the subscript parentheses, because john.residence is the optional value that the optional chain is trying to get.
Example 2
Instance to create a Residence
give an example to john.residence
and in his rooms
. There are one or more objects in the array Room
instance, then you can use the optional chain to pass the Residence
thesubscript script is obtained in the rooms
the instance in the array:
class Person {
var residence: Residence?
}
// Defined a variable 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 {
return rooms[i]
}
func printNumberOfRooms() {
print("Room number is \(numberOfRooms)")
}
var address: Address?
}
// Room defines a name attribute and an initializer that sets the room name
class Room {
let name: String
init(name: String) { self.name = name }
}
// The final class in the model is called Address
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if (buildingName != nil) {
return buildingName
} else if (buildingNumber != nil) {
return buildingNumber
} else {
return nil
}
}
}
let john = Person()
let johnsHouse = Residence()
johnsHouse.rooms.append(Room(name: "living room"))
johnsHouse.rooms.append(Room(name: "kitchen"))
john.residence = johnsHouse
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 is \(johnsStreet)。")
} else {
print("Unable to retrieve address. ")
}
The output of the above program execution is as follows:
John's street is Laurel Street。
Access the subscript through an optional link call
Through the optional link call, we can use the subscript to read or write the optional value and determine whether the subscript call is successful ornot.
Example
class Person {
var residence: Residence?
}
// Defined a variable 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 {
return rooms[i]
}
func printNumberOfRooms() {
print("Room number is \(numberOfRooms)")
}
var address: Address?
}
// Room defines a name attribute and an initializer that sets the room name
class Room {
let name: String
init(name: String) { self.name = name }
}
// The final class in the model is called Address
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if (buildingName != nil) {
return buildingName
} else if (buildingNumber != nil) {
return buildingNumber
} else {
return nil
}
}
}
let john = Person()
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 is named\(firstRoomName)")
} else {
print("Unable to retrieve room")
}
The output of the above program execution is as follows:
The first room is called the living room
Access the subscript of the optional type
If the subscript returns a nullable type value, such as in Swift Dictionary
of key
subscript. You can put a question mark after the closing parentheses of the subscript to link the nullable return value of the subscript:
var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
testScores["Dave"]?[0] = 91
testScores["Bev"]?[0]++
testScores["Brian"]?[0] = 72
// the "Dave" array is now [91, 82, 84] and the "Bev" array is now [80, 94, 81]
In the above example, a testScores
array, which contains two key-value pairs, set the String
of type key
maps to an array of integers.
This example uses an optional link call to set the first element in the “Dave” array to 91, the first element in the “Bev” array + 1, and then tries to set the first element in the “Brian” array to 72.
The first two calls are successful because these two key exist. But the key “Brian” does not exist in the dictionary, so the third call fails.
Connect multi-layer links
You can connect multiple layers of optional chains together, and you can digup property methods and subscript scripts at a lower level in the model. However, the multi-layer optional chain cannot add more layers than the optional values that have been returned.
If you try to get it through the optional chain Int
value, no matter how many layers of links are used, the Int?
. Similarly, if you try to get through the optional chain Int?
value, no matter how many layers oflinks are used, the Int?
.
Example 1
The following example attempts to get the john
of residence
in theattribute address
of street
property. Two layers of optional chains are used to contact. residence
and address
property, both of which are optional types
class Person {
var residence: Residence?
}
// Defined a variable 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 {
return rooms[i]
}
func printNumberOfRooms() {
print("Room number is \(numberOfRooms)")
}
var address: Address?
}
// Room defines a name attribute and an initializer that sets the room name
class Room {
let name: String
init(name: String) { self.name = name }
}
// The final class in the model is called Address
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if (buildingName != nil) {
return buildingName
} else if (buildingNumber != nil) {
return buildingNumber
} else {
return nil
}
}
}
let john = Person()
if let johnsStreet = john.residence?.address?.street {
print("John's address is \(johnsStreet).")
} else {
print("Unable to retrieve address")
}
The output of the above program execution is as follows:
Unable to retrieve address
Example 2
If you work for Address
set an instance to act as john.residence.address
and the value of address
of street
property sets an actual value, which you can get through a multi-layer optional chain.
class Person {
var residence: Residence?
}
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("Room number is \(numberOfRooms)")
}
var address: Address?
}
class Room {
let name: String
init(name: String) { self.name = name }
}
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if (buildingName != nil) {
return buildingName
} else if (buildingNumber != nil) {
return buildingNumber
} else {
return nil
}
}
}
let john = Person()
john.residence?[0] = Room(name: "bathroom")
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 is \(firstRoomName)")
} else {
print("Unable to retrieve room")
}
The output result of the above example is:
The first room is the living room
Link functions that return optional values
We can also call methods that return nullable values through optional links,and we can continue to link optional values.
Example
class Person {
var residence: Residence?
}
// Defined a variable 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 {
return rooms[i]
}
func printNumberOfRooms() {
print("Room number is \(numberOfRooms)")
}
var address: Address?
}
// Room defines a name attribute and an initializer that sets the room name
class Room {
let name: String
init(name: String) { self.name = name }
}
// The final class in the model is called Address
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if (buildingName != nil) {
return buildingName
} else if (buildingNumber != nil) {
return buildingNumber
} else {
return nil
}
}
}
let john = Person()
if john.residence?.printNumberOfRooms() != nil {
print("Room number specified)")
} else {
print("No room specified room number")
}
The output of the above program execution is as follows:
No room specified room number