Swift optional chaining


Optional Chaining is a process that can request and call properties, methods and subscripts. The target for requesting or calling may be nil.

The optional chain returns two values:

  • If the target has a value, the call will succeed and the value will be returned

  • If the target is nil, the call will return nil

Multiple requests or calls can be linked into a chain. If any node is nil, the entire chain will fail.


Optional chaining can replace forced parsing

You can define an optional by placing a question mark (?) after the optional value of a property, method, or subscript script chain.

Optional chain '?'Exclamation mark (!) forces expansion of methods, properties, subscripts Optional chain
? Place it in an optional value and then call the method, attribute, and subscript script! Place it in the optional value and then call the method, attribute, and subscript script to force expansion of the value
Output a friendly error message when optional is nilForce expansion execution error when optional is nil

Use exclamation mark (!) optional chain example

class Person {
    var residence: Residence?
}

class Residence {
    var numberOfRooms = 1
}

let john = Person()

//将导致运行时错误
let roomCount = john.residence!.numberOfRooms

The above program execution output result is:

fatal error: unexpectedly found nil while unwrapping an Optional value


I want to use exclamation mark (!) to force parsing to obtain this person The residence attribute numberOfRooms attribute value will cause a runtime error because there is no residence value that can be parsed at this time.


Use exclamation point (!) optional chain example

class Person {
    var residence: Residence?
}

class Residence {
    var numberOfRooms = 1
}

let john = Person()

// 链接可选residence?属性,如果residence存在则取回numberOfRooms的值
if let roomCount = john.residence?.numberOfRooms {
    print("John 的房间号为 \(roomCount)。")
} else {
    print("不能查看房间号")
}

The above program execution output result is:

不能查看房间号

Because of this operation of trying to obtain numberOfRooms If it fails, the optional chain will return a value of type Int?, also known as an "optional Int". When residence is empty (the above example), the selected Int will be empty, so numberOfRooms will not be accessible.

It should be noted that this is true even when numberOfRooms is a non-optional Int (Int?). As long as the request is through the optional chain, it means that the final numberOfRooms always returns an Int? instead of an Int.


Define model classes for optional chains

You can use optional chains to call properties, methods, and subscripts at multiple levels. This allows you to leverage complex models between them to get lower-level properties and check whether you can successfully get such low-level properties.

Instance

Defines four model classes, including multi-layer optional chains:

class Person {
    var residence: Residence?
}

// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
    var rooms = [Room]()
    var numberOfRooms: Int {
        return rooms.count
    }
    subscript(i: Int) -> Room {
        return rooms[i]
    }
    func printNumberOfRooms() {
        print("房间号为 \(numberOfRooms)")
    }
    var address: Address?
}

// Room 定义一个name属性和一个设定room名的初始化器
class Room {
    let name: String
    init(name: String) { self.name = name }
}

// 模型中的最终类叫做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
        }
    }
}

Calling methods through optional chains

You can use optional chaining to call methods on optional values ​​and check whether the method call was successful. Even if this method does not return a value, you can still use optional chaining for this purpose.

class Person {
    var residence: Residence?
}

// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
    var rooms = [Room]()
    var numberOfRooms: Int {
        return rooms.count
    }
    subscript(i: Int) -> Room {
        return rooms[i]
    }
    func printNumberOfRooms() {
        print("房间号为 \(numberOfRooms)")
    }
    var address: Address?
}

// Room 定义一个name属性和一个设定room名的初始化器
class Room {
    let name: String
    init(name: String) { self.name = name }
}

// 模型中的最终类叫做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("输出房间号")
} else {
    print("无法输出房间号")
}

The execution output of the above program is:

无法输出房间号

Use the if statement to check whether the printNumberOfRooms method can be successfully called: If the method is successfully called through the optional chain, the implicit return value of printNumberOfRooms will be Is Void, if not successful, nil will be returned.


Using optional chaining to call the subscript script

You can use optional chaining to try to get the value from the subscript script and check whether the call to the subscript script was successful, however, you Subscript scripts cannot be set via optional chaining.

Example 1

class Person {
    var residence: Residence?
}

// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
    var rooms = [Room]()
    var numberOfRooms: Int {
        return rooms.count
    }
    subscript(i: Int) -> Room {
        return rooms[i]
    }
    func printNumberOfRooms() {
        print("房间号为 \(numberOfRooms)")
    }
    var address: Address?
}

// Room 定义一个name属性和一个设定room名的初始化器
class Room {
    let name: String
    init(name: String) { self.name = name }
}

// 模型中的最终类叫做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("第一个房间名 \(firstRoomName).")
} else {
    print("无法检索到房间")
}

The above program execution output result is:

无法检索到房间

The question mark of the optional chain in the subscript script call directly follows circname.print, in the subscript before the script parentheses, because circname.print is the optional value that the optional chain is trying to get.

Instance 2

In the example, create a Residence instance to john.residence, and there are one or more Room instances in his rooms array, then you can use the optional chain to download it through Residence The subscript is used to obtain the instance in the rooms array:

class Person {
    var residence: Residence?
}

// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
    var rooms = [Room]()
    var numberOfRooms: Int {
        return rooms.count
    }
    subscript(i: Int) -> Room {
        return rooms[i]
    }
    func printNumberOfRooms() {
        print("房间号为 \(numberOfRooms)")
    }
    var address: Address?
}

// Room 定义一个name属性和一个设定room名的初始化器
class Room {
    let name: String
    init(name: String) { self.name = name }
}

// 模型中的最终类叫做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: "客厅"))
johnsHouse.rooms.append(Room(name: "厨房"))
john.residence = johnsHouse

if let firstRoomName = john.residence?[0].name {
    print("第一个房间名为\(firstRoomName)")
} else {
    print("无法检索到房间")
}

The output result of the execution of the above program is:

第一个房间名为客厅

Access the subscript through the optional link call

Through optional link calls, we can use subscripts to read or write optional values, and determine whether the subscript call is successful.

Example

class Person {
    var residence: Residence?
}

// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
    var rooms = [Room]()
    var numberOfRooms: Int {
        return rooms.count
    }
    subscript(i: Int) -> Room {
        return rooms[i]
    }
    func printNumberOfRooms() {
        print("房间号为 \(numberOfRooms)")
    }
    var address: Address?
}

// Room 定义一个name属性和一个设定room名的初始化器
class Room {
    let name: String
    init(name: String) { self.name = name }
}

// 模型中的最终类叫做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: "客厅"))
johnsHouse.rooms.append(Room(name: "厨房"))
john.residence = johnsHouse

if let firstRoomName = john.residence?[0].name {
    print("第一个房间名为\(firstRoomName)")
} else {
    print("无法检索到房间")
}

The output result of the execution of the above program is:

第一个房间名为客厅

Accessing the optional type of subscript

If the subscript is returned, it can be null Type value, such as the key subscript of Dictionary in Swift. You can link a subscript's nullable return value by placing a question mark after the subscript's closing bracket:

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]

The above example defines a testScores array, which contains two key-value pairs and maps the String type key to an integer array.

This example uses optional chaining calls to set the first element in the "Dave" array to 91, +1 the first element in the "Bev" array, and then attempts to set the first element in the "Brian" array One element is set to 72.

The first two calls are successful because these two keys exist. But key "Brian" does not exist in the dictionary, so the third call fails.


Connecting multi-layer links

You can connect multi-layer optional chains together, and you can mine the lower-level attribute methods and subscript scripts in the model. However, a multi-level optional chain cannot add more levels than the optional values ​​already returned.

If you try to get an Int value through optional chaining, no matter how many levels of chaining are used, the result will always be Int?. Similarly, if you try to get an Int? value through optional chaining, the result will always be an Int?, no matter how many levels of chaining are used.

Example 1

The following example attempts to obtain the street attribute of address in john's residence attribute. Two layers of optional chains are used here to connect the residence and address attributes, both of which are optional types:

class Person {
    var residence: Residence?
}

// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
    var rooms = [Room]()
    var numberOfRooms: Int {
        return rooms.count
    }
    subscript(i: Int) -> Room {
        return rooms[i]
    }
    func printNumberOfRooms() {
        print("房间号为 \(numberOfRooms)")
    }
    var address: Address?
}

// Room 定义一个name属性和一个设定room名的初始化器
class Room {
    let name: String
    init(name: String) { self.name = name }
}

// 模型中的最终类叫做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 的地址为 \(johnsStreet).")
} else {
    print("不能检索地址")
}

The output result of the above program execution is:

不能检索地址

Example 2

If you set an instance of Address as the value of john.residence.address, and set an actual value for the street attribute of address, you can get the attribute value through multiple optional chains.

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("房间号为 \(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: "浴室")

let johnsHouse = Residence()
johnsHouse.rooms.append(Room(name: "客厅"))
johnsHouse.rooms.append(Room(name: "厨房"))
john.residence = johnsHouse

if let firstRoomName = john.residence?[0].name {
    print("第一个房间是\(firstRoomName)")
} else {
    print("无法检索房间")
}

The output result of the above example is:

第一个房间是客厅

Link the function that returns an optional value

We can also call the return nullable value through the optional link value, and can continue to chain optional values.

Example

class Person {
    var residence: Residence?
}

// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
    var rooms = [Room]()
    var numberOfRooms: Int {
        return rooms.count
    }
    subscript(i: Int) -> Room {
        return rooms[i]
    }
    func printNumberOfRooms() {
        print("房间号为 \(numberOfRooms)")
    }
    var address: Address?
}

// Room 定义一个name属性和一个设定room名的初始化器
class Room {
    let name: String
    init(name: String) { self.name = name }
}

// 模型中的最终类叫做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("指定了房间号)")
}  else {
    print("未指定房间号")
}

The execution output of the above program is:

未指定房间号