Swift 可選鏈

可選鏈(Optional Chaining)是一種是一種可以請(qǐng)求和調(diào)用屬性、方法和子腳本的過(guò)程,用于請(qǐng)求或調(diào)用的目標(biāo)可能為nil。

可選鏈返回兩個(gè)值:

  • 如果目標(biāo)有值,調(diào)用就會(huì)成功,返回該值

  • 如果目標(biāo)為nil,調(diào)用將返回nil

多次請(qǐng)求或調(diào)用可以被鏈接成一個(gè)鏈,如果任意一個(gè)節(jié)點(diǎn)為nil將導(dǎo)致整條鏈?zhǔn)А?/p>


可選鏈可替代強(qiáng)制解析

通過(guò)在屬性、方法、或下標(biāo)腳本的可選值后面放一個(gè)問(wèn)號(hào)(?),即可定義一個(gè)可選鏈。

可選鏈 '?' 感嘆號(hào)(!)強(qiáng)制展開(kāi)方法,屬性,下標(biāo)腳本可選鏈
? 放置于可選值后來(lái)調(diào)用方法,屬性,下標(biāo)腳本 ! 放置于可選值后來(lái)調(diào)用方法,屬性,下標(biāo)腳本來(lái)強(qiáng)制展開(kāi)值
當(dāng)可選為 nil 輸出比較友好的錯(cuò)誤信息 當(dāng)可選為 nil 時(shí)強(qiáng)制展開(kāi)執(zhí)行錯(cuò)誤

使用感嘆號(hào)(!)可選鏈實(shí)例

class Person {
    var residence: Residence?
}

class Residence {
    var numberOfRooms = 1
}

let john = Person()

//將導(dǎo)致運(yùn)行時(shí)錯(cuò)誤
let roomCount = john.residence!.numberOfRooms

以上程序執(zhí)行輸出結(jié)果為:

fatal error: unexpectedly found nil while unwrapping an Optional value

想使用感嘆號(hào)(!)強(qiáng)制解析獲得這個(gè)人residence屬性numberOfRooms屬性值,將會(huì)引發(fā)運(yùn)行時(shí)錯(cuò)誤,因?yàn)檫@時(shí)沒(méi)有可以供解析的residence值。

使用感嘆號(hào)(!)可選鏈實(shí)例

class Person {
    var residence: Residence?
}

class Residence {
    var numberOfRooms = 1
}

let john = Person()

// 鏈接可選residence?屬性,如果residence存在則取回numberOfRooms的值
if let roomCount = john.residence?.numberOfRooms {
    print("John 的房間號(hào)為 \(roomCount)。")
} else {
    print("不能查看房間號(hào)")
}

以上程序執(zhí)行輸出結(jié)果為:

不能查看房間號(hào)

因?yàn)檫@種嘗試獲得numberOfRooms的操作有可能失敗,可選鏈會(huì)返回Int?類(lèi)型值,或者稱(chēng)作"可選Int"。當(dāng)residence是空的時(shí)候(上例),選擇Int將會(huì)為空,因此會(huì)出現(xiàn)無(wú)法訪問(wèn)numberOfRooms的情況。

要注意的是,即使numberOfRooms是非可選Int(Int?)時(shí)這一點(diǎn)也成立。只要是通過(guò)可選鏈的請(qǐng)求就意味著最后numberOfRooms總是返回一個(gè)Int?而不是Int。


為可選鏈定義模型類(lèi)

你可以使用可選鏈來(lái)多層調(diào)用屬性,方法,和下標(biāo)腳本。這讓你可以利用它們之間的復(fù)雜模型來(lái)獲取更底層的屬性,并檢查是否可以成功獲取此類(lèi)底層屬性。

實(shí)例

定義了四個(gè)模型類(lèi),其中包括多層可選鏈:

class Person {
    var residence: Residence?
}

// 定義了一個(gè)變量 rooms,它被初始化為一個(gè)Room[]類(lèi)型的空數(shù)組
class Residence {
    var rooms = [Room]()
    var numberOfRooms: Int {
        return rooms.count
    }
    subscript(i: Int) -> Room {
        return rooms[i]
    }
    func printNumberOfRooms() {
        print("房間號(hào)為 \(numberOfRooms)")
    }
    var address: Address?
}

// Room 定義一個(gè)name屬性和一個(gè)設(shè)定room名的初始化器
class Room {
    let name: String
    init(name: String) { self.name = name }
}

// 模型中的最終類(lèi)叫做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
        }
    }
}

通過(guò)可選鏈調(diào)用方法

你可以使用可選鏈的來(lái)調(diào)用可選值的方法并檢查方法調(diào)用是否成功。即使這個(gè)方法沒(méi)有返回值,你依然可以使用可選鏈來(lái)達(dá)成這一目的。

class Person {
    var residence: Residence?
}

// 定義了一個(gè)變量 rooms,它被初始化為一個(gè)Room[]類(lèi)型的空數(shù)組
class Residence {
    var rooms = [Room]()
    var numberOfRooms: Int {
        return rooms.count
    }
    subscript(i: Int) -> Room {
        return rooms[i]
    }
    func printNumberOfRooms() {
        print("房間號(hào)為 \(numberOfRooms)")
    }
    var address: Address?
}

// Room 定義一個(gè)name屬性和一個(gè)設(shè)定room名的初始化器
class Room {
    let name: String
    init(name: String) { self.name = name }
}

// 模型中的最終類(lèi)叫做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("輸出房間號(hào)")
} else {
    print("無(wú)法輸出房間號(hào)")
}

以上程序執(zhí)行輸出結(jié)果為:

無(wú)法輸出房間號(hào)

使用if語(yǔ)句來(lái)檢查是否能成功調(diào)用printNumberOfRooms方法:如果方法通過(guò)可選鏈調(diào)用成功,printNumberOfRooms的隱式返回值將會(huì)是Void,如果沒(méi)有成功,將返回nil。


使用可選鏈調(diào)用下標(biāo)腳本

你可以使用可選鏈來(lái)嘗試從下標(biāo)腳本獲取值并檢查下標(biāo)腳本的調(diào)用是否成功,然而,你不能通過(guò)可選鏈來(lái)設(shè)置下標(biāo)腳本。

實(shí)例1

class Person {
    var residence: Residence?
}

// 定義了一個(gè)變量 rooms,它被初始化為一個(gè)Room[]類(lèi)型的空數(shù)組
class Residence {
    var rooms = [Room]()
    var numberOfRooms: Int {
        return rooms.count
    }
    subscript(i: Int) -> Room {
        return rooms[i]
    }
    func printNumberOfRooms() {
        print("房間號(hào)為 \(numberOfRooms)")
    }
    var address: Address?
}

// Room 定義一個(gè)name屬性和一個(gè)設(shè)定room名的初始化器
class Room {
    let name: String
    init(name: String) { self.name = name }
}

// 模型中的最終類(lèi)叫做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("第一個(gè)房間名 \(firstRoomName).")
} else {
    print("無(wú)法檢索到房間")
}

以上程序執(zhí)行輸出結(jié)果為:

無(wú)法檢索到房間

在下標(biāo)腳本調(diào)用中可選鏈的問(wèn)號(hào)直接跟在 circname.print 的后面,在下標(biāo)腳本括號(hào)的前面,因?yàn)閏ircname.print是可選鏈試圖獲得的可選值。

實(shí)例2

實(shí)例中創(chuàng)建一個(gè)Residence實(shí)例給john.residence,且在他的rooms數(shù)組中有一個(gè)或多個(gè)Room實(shí)例,那么你可以使用可選鏈通過(guò)Residence下標(biāo)腳本來(lái)獲取在rooms數(shù)組中的實(shí)例了:

class Person {
    var residence: Residence?
}

// 定義了一個(gè)變量 rooms,它被初始化為一個(gè)Room[]類(lèi)型的空數(shù)組
class Residence {
    var rooms = [Room]()
    var numberOfRooms: Int {
        return rooms.count
    }
    subscript(i: Int) -> Room {
        return rooms[i]
    }
    func printNumberOfRooms() {
        print("房間號(hào)為 \(numberOfRooms)")
    }
    var address: Address?
}

// Room 定義一個(gè)name屬性和一個(gè)設(shè)定room名的初始化器
class Room {
    let name: String
    init(name: String) { self.name = name }
}

// 模型中的最終類(lèi)叫做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("第一個(gè)房間名為\(firstRoomName)")
} else {
    print("無(wú)法檢索到房間")
}

以上程序執(zhí)行輸出結(jié)果為:

第一個(gè)房間名為客廳

通過(guò)可選鏈接調(diào)用來(lái)訪問(wèn)下標(biāo)

通過(guò)可選鏈接調(diào)用,我們可以用下標(biāo)來(lái)對(duì)可選值進(jìn)行讀取或?qū)懭耄⑶遗袛嘞聵?biāo)調(diào)用是否成功。

實(shí)例

class Person {
    var residence: Residence?
}

// 定義了一個(gè)變量 rooms,它被初始化為一個(gè)Room[]類(lèi)型的空數(shù)組
class Residence {
    var rooms = [Room]()
    var numberOfRooms: Int {
        return rooms.count
    }
    subscript(i: Int) -> Room {
        return rooms[i]
    }
    func printNumberOfRooms() {
        print("房間號(hào)為 \(numberOfRooms)")
    }
    var address: Address?
}

// Room 定義一個(gè)name屬性和一個(gè)設(shè)定room名的初始化器
class Room {
    let name: String
    init(name: String) { self.name = name }
}

// 模型中的最終類(lèi)叫做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("第一個(gè)房間名為\(firstRoomName)")
} else {
    print("無(wú)法檢索到房間")
}

以上程序執(zhí)行輸出結(jié)果為:

第一個(gè)房間名為客廳

訪問(wèn)可選類(lèi)型的下標(biāo)

如果下標(biāo)返回可空類(lèi)型值,比如Swift中Dictionary的key下標(biāo)??梢栽谙聵?biāo)的閉合括號(hào)后面放一個(gè)問(wèn)號(hào)來(lái)鏈接下標(biāo)的可空返回值:

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]

上面的例子中定義了一個(gè)testScores數(shù)組,包含了兩個(gè)鍵值對(duì), 把String類(lèi)型的key映射到一個(gè)整形數(shù)組。

這個(gè)例子用可選鏈接調(diào)用把"Dave"數(shù)組中第一個(gè)元素設(shè)為91,把"Bev"數(shù)組的第一個(gè)元素+1,然后嘗試把"Brian"數(shù)組中的第一個(gè)元素設(shè)為72。

前兩個(gè)調(diào)用是成功的,因?yàn)檫@兩個(gè)key存在。但是key"Brian"在字典中不存在,所以第三個(gè)調(diào)用失敗。


連接多層鏈接

你可以將多層可選鏈連接在一起,可以掘取模型內(nèi)更下層的屬性方法和下標(biāo)腳本。然而多層可選鏈不能再添加比已經(jīng)返回的可選值更多的層。

如果你試圖通過(guò)可選鏈獲得Int值,不論使用了多少層鏈接返回的總是Int?。 相似的,如果你試圖通過(guò)可選鏈獲得Int?值,不論使用了多少層鏈接返回的總是Int?。

實(shí)例1

下面的例子試圖獲取john的residence屬性里的address的street屬性。這里使用了兩層可選鏈來(lái)聯(lián)系residence和address屬性,它們兩者都是可選類(lèi)型:

class Person {
    var residence: Residence?
}

// 定義了一個(gè)變量 rooms,它被初始化為一個(gè)Room[]類(lèi)型的空數(shù)組
class Residence {
    var rooms = [Room]()
    var numberOfRooms: Int {
        return rooms.count
    }
    subscript(i: Int) -> Room {
        return rooms[i]
    }
    func printNumberOfRooms() {
        print("房間號(hào)為 \(numberOfRooms)")
    }
    var address: Address?
}

// Room 定義一個(gè)name屬性和一個(gè)設(shè)定room名的初始化器
class Room {
    let name: String
    init(name: String) { self.name = name }
}

// 模型中的最終類(lèi)叫做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("不能檢索地址")
}

以上程序執(zhí)行輸出結(jié)果為:

不能檢索地址

實(shí)例2

如果你為Address設(shè)定一個(gè)實(shí)例來(lái)作為john.residence.address的值,并為address的street屬性設(shè)定一個(gè)實(shí)際值,你可以通過(guò)多層可選鏈來(lái)得到這個(gè)屬性值。

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("房間號(hào)為 \(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("第一個(gè)房間是\(firstRoomName)")
} else {
    print("無(wú)法檢索房間")
}

以上實(shí)例輸出結(jié)果為:

第一個(gè)房間是客廳

對(duì)返回可選值的函數(shù)進(jìn)行鏈接

我們還可以通過(guò)可選鏈接來(lái)調(diào)用返回可空值的方法,并且可以繼續(xù)對(duì)可選值進(jìn)行鏈接。

實(shí)例

class Person {
    var residence: Residence?
}

// 定義了一個(gè)變量 rooms,它被初始化為一個(gè)Room[]類(lèi)型的空數(shù)組
class Residence {
    var rooms = [Room]()
    var numberOfRooms: Int {
        return rooms.count
    }
    subscript(i: Int) -> Room {
        return rooms[i]
    }
    func printNumberOfRooms() {
        print("房間號(hào)為 \(numberOfRooms)")
    }
    var address: Address?
}

// Room 定義一個(gè)name屬性和一個(gè)設(shè)定room名的初始化器
class Room {
    let name: String
    init(name: String) { self.name = name }
}

// 模型中的最終類(lèi)叫做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("指定了房間號(hào))")
}  else {
    print("未指定房間號(hào)")
}

以上程序執(zhí)行輸出結(jié)果為:

未指定房間號(hào)