鍍金池/ 教程/ iOS/ 可選鏈?zhǔn)秸{(diào)用
特性(Attributes)
Access Control 權(quán)限控制的黑與白
基本運(yùn)算符(Basic Operators)
基礎(chǔ)部分(The Basics)
閉包(Closures)
擴(kuò)展
泛型參數(shù)(Generic Parameters and Arguments)
訪問(wèn)控制和 protected
語(yǔ)句(Statements)
模式(Patterns)
WWDC 里面的那個(gè)“大炮打氣球”
關(guān)于語(yǔ)言參考(About the Language Reference)
語(yǔ)法總結(jié)(Summary of the Grammar)
嵌套類型
類型(Types)
Swift 初見(jiàn)(A Swift Tour)
泛型
枚舉(Enumerations)
高級(jí)運(yùn)算符
繼承
析構(gòu)過(guò)程
關(guān)于 Swift(About Swift)
訪問(wèn)控制
類和結(jié)構(gòu)體
內(nèi)存安全
Swift 與 C 語(yǔ)言指針友好合作
協(xié)議
屬性(Properties)
可選類型完美解決占位問(wèn)題
錯(cuò)誤處理
字符串和字符(Strings and Characters)
聲明(Declarations)
自動(dòng)引用計(jì)數(shù)
Swift 里的值類型與引用類型
表達(dá)式(Expressions)
Swift 文檔修訂歷史
造個(gè)類型不是夢(mèng)-白話 Swift 類型創(chuàng)建
歡迎使用 Swift
詞法結(jié)構(gòu)(Lexical Structure)
集合類型(Collection Types)
下標(biāo)
方法(Methods)
可選鏈?zhǔn)秸{(diào)用
版本兼容性
類型轉(zhuǎn)換
構(gòu)造過(guò)程
The Swift Programming Language 中文版
函數(shù)(Functions)
Swift 教程
控制流(Control Flow)

可選鏈?zhǔn)秸{(diào)用


1.0 翻譯:Jasonbroker 校對(duì):numbbbbb, stanzhai

2.0 翻譯+校對(duì):lyojo

2.1 校對(duì):shanks,2015-10-31

2.2 翻譯+校對(duì):SketchK 2016-05-15

3.0.1,shanks,2016-11-13

4.0 校對(duì):kemchenj 2017-09-21

4.1 翻譯+校對(duì):mylittleswift

本頁(yè)包含內(nèi)容:

可選鏈?zhǔn)秸{(diào)用是一種可以在當(dāng)前值可能為 nil 的可選值上請(qǐng)求和調(diào)用屬性、方法及下標(biāo)的方法。如果可選值有值,那么調(diào)用就會(huì)成功;如果可選值是 nil,那么調(diào)用將返回 nil。多個(gè)調(diào)用可以連接在一起形成一個(gè)調(diào)用鏈,如果其中任何一個(gè)節(jié)點(diǎn)為 nil,整個(gè)調(diào)用鏈都會(huì)失敗,即返回 nil

注意

Swift 的可選鏈?zhǔn)秸{(diào)用和 Objective-C 中向 nil 發(fā)送消息有些相像,但是 Swift 的可選鏈?zhǔn)秸{(diào)用可以應(yīng)用于任意類型,并且能檢查調(diào)用是否成功。

使用可選鏈?zhǔn)秸{(diào)用代替強(qiáng)制展開(kāi)

通過(guò)在想調(diào)用的屬性、方法,或下標(biāo)的可選值后面放一個(gè)問(wèn)號(hào)(?),可以定義一個(gè)可選鏈。這一點(diǎn)很像在可選值后面放一個(gè)嘆號(hào)(!)來(lái)強(qiáng)制展開(kāi)它的值。它們的主要區(qū)別在于當(dāng)可選值為空時(shí)可選鏈?zhǔn)秸{(diào)用只會(huì)調(diào)用失敗,然而強(qiáng)制展開(kāi)將會(huì)觸發(fā)運(yùn)行時(shí)錯(cuò)誤。

為了反映可選鏈?zhǔn)秸{(diào)用可以在空值(nil)上調(diào)用的事實(shí),不論這個(gè)調(diào)用的屬性、方法及下標(biāo)返回的值是不是可選值,它的返回結(jié)果都是一個(gè)可選值。你可以利用這個(gè)返回值來(lái)判斷你的可選鏈?zhǔn)秸{(diào)用是否調(diào)用成功,如果調(diào)用有返回值則說(shuō)明調(diào)用成功,返回 nil 則說(shuō)明調(diào)用失敗。

特別地,可選鏈?zhǔn)秸{(diào)用的返回結(jié)果與原本的返回結(jié)果具有相同的類型,但是被包裝成了一個(gè)可選值。例如,使用可選鏈?zhǔn)秸{(diào)用訪問(wèn)屬性,當(dāng)可選鏈?zhǔn)秸{(diào)用成功時(shí),如果屬性原本的返回結(jié)果是 Int 類型,則會(huì)變?yōu)?Int? 類型。

下面幾段代碼將解釋可選鏈?zhǔn)秸{(diào)用和強(qiáng)制展開(kāi)的不同。

首先定義兩個(gè)類 PersonResidence

class Person {
    var residence: Residence?
}

class Residence {
    var numberOfRooms = 1
}

Residence 有一個(gè) Int 類型的屬性 numberOfRooms,其默認(rèn)值為 1。Person 具有一個(gè)可選的 residence 屬性,其類型為 Residence?。

假如你創(chuàng)建了一個(gè)新的 Person 實(shí)例,它的 residence 屬性由于是是可選型而將初始化為 nil,在下面的代碼中,john 有一個(gè)值為 nilresidence 屬性:

let john = Person()

如果使用嘆號(hào)(!)強(qiáng)制展開(kāi)獲得這個(gè) johnresidence 屬性中的 numberOfRooms 值,會(huì)觸發(fā)運(yùn)行時(shí)錯(cuò)誤,因?yàn)檫@時(shí) residence 沒(méi)有可以展開(kāi)的值:

let roomCount = john.residence!.numberOfRooms
// 這會(huì)引發(fā)運(yùn)行時(shí)錯(cuò)誤

john.residence 為非 nil 值的時(shí)候,上面的調(diào)用會(huì)成功,并且把 roomCount 設(shè)置為 Int 類型的房間數(shù)量。正如上面提到的,當(dāng) residencenil 的時(shí)候上面這段代碼會(huì)觸發(fā)運(yùn)行時(shí)錯(cuò)誤。

可選鏈?zhǔn)秸{(diào)用提供了另一種訪問(wèn) numberOfRooms 的方式,使用問(wèn)號(hào)(?)來(lái)替代原來(lái)的嘆號(hào)(!):

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.”

residence 后面添加問(wèn)號(hào)之后,Swift 就會(huì)在 residence 不為 nil 的情況下訪問(wèn) numberOfRooms。

因?yàn)樵L問(wèn) numberOfRooms 有可能失敗,可選鏈?zhǔn)秸{(diào)用會(huì)返回 Int? 類型,或稱為“可選的 Int”。如上例所示,當(dāng) residencenil 的時(shí)候,可選的 Int 將會(huì)為 nil,表明無(wú)法訪問(wèn) numberOfRooms。訪問(wèn)成功時(shí),可選的 Int 值會(huì)通過(guò)可選綁定展開(kāi),并賦值給非可選類型的 roomCount 常量。

要注意的是,即使 numberOfRooms 是非可選的 Int 時(shí),這一點(diǎn)也成立。只要使用可選鏈?zhǔn)秸{(diào)用就意味著 numberOfRooms 會(huì)返回一個(gè) Int? 而不是 Int。

可以將一個(gè) Residence 的實(shí)例賦給 john.residence,這樣它就不再是 nil 了:

john.residence = Residence()

john.residence 現(xiàn)在包含一個(gè)實(shí)際的 Residence 實(shí)例,而不再是 nil。如果你試圖使用先前的可選鏈?zhǔn)秸{(diào)用訪問(wèn) numberOfRooms,它現(xiàn)在將返回值為 1Int? 類型的值:

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).”

為可選鏈?zhǔn)秸{(diào)用定義模型類

通過(guò)使用可選鏈?zhǔn)秸{(diào)用可以調(diào)用多層屬性、方法和下標(biāo)。這樣可以在復(fù)雜的模型中向下訪問(wèn)各種子屬性,并且判斷能否訪問(wèn)子屬性的屬性、方法或下標(biāo)。

下面這段代碼定義了四個(gè)模型類,這些例子包括多層可選鏈?zhǔn)秸{(diào)用。為了方便說(shuō)明,在 PersonResidence 的基礎(chǔ)上增加了 Room 類和 Address 類,以及相關(guān)的屬性、方法以及下標(biāo)。

Person 類的定義基本保持不變:

class Person {
    var residence: Residence?
}

Residence 類比之前復(fù)雜些,增加了一個(gè)名為 rooms 的變量屬性,該屬性被初始化為 [Room] 類型的空數(shù)組:

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?
}

現(xiàn)在 Residence 有了一個(gè)存儲(chǔ) Room 實(shí)例的數(shù)組,numberOfRooms 屬性被實(shí)現(xiàn)為計(jì)算型屬性,而不是存儲(chǔ)型屬性。numberOfRooms 屬性簡(jiǎn)單地返回 rooms 數(shù)組的 count 屬性的值。

Residence 還提供了訪問(wèn) rooms 數(shù)組的快捷方式,即提供可讀寫的下標(biāo)來(lái)訪問(wèn) rooms 數(shù)組中指定位置的元素。

此外,Residence 還提供了 printNumberOfRooms 方法,這個(gè)方法的作用是打印 numberOfRooms 的值。

最后,Residence 還定義了一個(gè)可選屬性 address,其類型為 Address?。Address 類的定義在下面會(huì)說(shuō)明。

Room 類是一個(gè)簡(jiǎn)單類,其實(shí)例被存儲(chǔ)在 rooms 數(shù)組中。該類只包含一個(gè)屬性 name,以及一個(gè)用于將該屬性設(shè)置為適當(dāng)?shù)姆块g名的初始化函數(shù):

class Room {
    let name: String
    init(name: String) { self.name = name }
}

最后一個(gè)類是 Address,這個(gè)類有三個(gè) String? 類型的可選屬性。buildingName 以及 buildingNumber 屬性分別表示某個(gè)大廈的名稱和號(hào)碼,第三個(gè)屬性 street 表示大廈所在街道的名稱:

class Address {
    var buildingName: String?
    var buildingNumber: String?
    var street: String?
    func buildingIdentifier() -> String? {
        if buildingName != nil {
            return buildingName
        } else if buildingNumber != nil && street != nil {
            return "\(buildingNumber) \(street)"
        } else {
            return nil
        }
    }
}

Address 類提供了 buildingIdentifier() 方法,返回值為 String?。 如果 buildingName 有值則返回 buildingName?;蛘?,如果 buildingNumberstreet 均有值則返回 buildingNumber。否則,返回 nil。

通過(guò)可選鏈?zhǔn)秸{(diào)用訪問(wèn)屬性

正如使用可選鏈?zhǔn)秸{(diào)用代替強(qiáng)制展開(kāi)中所述,可以通過(guò)可選鏈?zhǔn)秸{(diào)用在一個(gè)可選值上訪問(wèn)它的屬性,并判斷訪問(wèn)是否成功。

下面的代碼創(chuàng)建了一個(gè) Person 實(shí)例,然后像之前一樣,嘗試訪問(wèn) numberOfRooms 屬性:

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.”

因?yàn)?john.residencenil,所以這個(gè)可選鏈?zhǔn)秸{(diào)用依舊會(huì)像先前一樣失敗。

還可以通過(guò)可選鏈?zhǔn)秸{(diào)用來(lái)設(shè)置屬性值:

let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
john.residence?.address = someAddress

在這個(gè)例子中,通過(guò) john.residence 來(lái)設(shè)定 address 屬性也會(huì)失敗,因?yàn)?john.residence 當(dāng)前為 nil。

上面代碼中的賦值過(guò)程是可選鏈?zhǔn)秸{(diào)用的一部分,這意味著可選鏈?zhǔn)秸{(diào)用失敗時(shí),等號(hào)右側(cè)的代碼不會(huì)被執(zhí)行。對(duì)于上面的代碼來(lái)說(shuō),很難驗(yàn)證這一點(diǎn),因?yàn)橄襁@樣賦值一個(gè)常量沒(méi)有任何副作用。下面的代碼完成了同樣的事情,但是它使用一個(gè)函數(shù)來(lái)創(chuàng)建 Address 實(shí)例,然后將該實(shí)例返回用于賦值。該函數(shù)會(huì)在返回前打印“Function was called”,這使你能驗(yàn)證等號(hào)右側(cè)的代碼是否被執(zhí)行。

func createAddress() -> Address {
    print("Function was called.")

    let someAddress = Address()
    someAddress.buildingNumber = "29"
    someAddress.street = "Acacia Road"

    return someAddress
}
john.residence?.address = createAddress()

沒(méi)有任何打印消息,可以看出 createAddress() 函數(shù)并未被執(zhí)行。

通過(guò)可選鏈?zhǔn)秸{(diào)用來(lái)調(diào)用方法

可以通過(guò)可選鏈?zhǔn)秸{(diào)用來(lái)調(diào)用方法,并判斷是否調(diào)用成功,即使這個(gè)方法沒(méi)有返回值。

Residence 類中的 printNumberOfRooms() 方法打印當(dāng)前的 numberOfRooms 值,如下所示:

func printNumberOfRooms() {
    print("The number of rooms is \(numberOfRooms)")
}

這個(gè)方法沒(méi)有返回值。然而,沒(méi)有返回值的方法具有隱式的返回類型 Void,如無(wú)返回值函數(shù)中所述。這意味著沒(méi)有返回值的方法也會(huì)返回 (),或者說(shuō)空的元組。

如果在可選值上通過(guò)可選鏈?zhǔn)秸{(diào)用來(lái)調(diào)用這個(gè)方法,該方法的返回類型會(huì)是 Void?,而不是 Void,因?yàn)橥ㄟ^(guò)可選鏈?zhǔn)秸{(diào)用得到的返回值都是可選的。這樣我們就可以使用 if 語(yǔ)句來(lái)判斷能否成功調(diào)用 printNumberOfRooms() 方法,即使方法本身沒(méi)有定義返回值。通過(guò)判斷返回值是否為 nil 可以判斷調(diào)用是否成功:

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.”

同樣的,可以據(jù)此判斷通過(guò)可選鏈?zhǔn)秸{(diào)用為屬性賦值是否成功。在上面的通過(guò)可選鏈?zhǔn)秸{(diào)用訪問(wèn)屬性的例子中,我們嘗試給 john.residence 中的 address 屬性賦值,即使 residencenil。通過(guò)可選鏈?zhǔn)秸{(diào)用給屬性賦值會(huì)返回 Void?,通過(guò)判斷返回值是否為 nil 就可以知道賦值是否成功:

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.”

通過(guò)可選鏈?zhǔn)秸{(diào)用訪問(wèn)下標(biāo)

通過(guò)可選鏈?zhǔn)秸{(diào)用,我們可以在一個(gè)可選值上訪問(wèn)下標(biāo),并且判斷下標(biāo)調(diào)用是否成功。

注意

通過(guò)可選鏈?zhǔn)秸{(diào)用訪問(wèn)可選值的下標(biāo)時(shí),應(yīng)該將問(wèn)號(hào)放在下標(biāo)方括號(hào)的前面而不是后面??蛇x鏈?zhǔn)秸{(diào)用的問(wèn)號(hào)一般直接跟在可選表達(dá)式的后面。

下面這個(gè)例子用下標(biāo)訪問(wèn) john.residence 屬性存儲(chǔ)的 Residence 實(shí)例的 rooms 數(shù)組中的第一個(gè)房間的名稱,因?yàn)?john.residencenil,所以下標(biāo)調(diào)用失敗了:

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.”

在這個(gè)例子中,問(wèn)號(hào)直接放在 john.residence 的后面,并且在方括號(hào)的前面,因?yàn)?john.residence 是可選值。

類似的,可以通過(guò)下標(biāo),用可選鏈?zhǔn)秸{(diào)用來(lái)賦值:

john.residence?[0] = Room(name: "Bathroom")

這次賦值同樣會(huì)失敗,因?yàn)?residence 目前是 nil。

如果你創(chuàng)建一個(gè) Residence 實(shí)例,并為其 rooms 數(shù)組添加一些 Room 實(shí)例,然后將 Residence 實(shí)例賦值給 john.residence,那就可以通過(guò)可選鏈和下標(biāo)來(lái)訪問(wèn)數(shù)組中的元素:

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.”

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

如果下標(biāo)返回可選類型值,比如 Swift 中 Dictionary 類型的鍵的下標(biāo),可以在下標(biāo)的結(jié)尾括號(hào)后面放一個(gè)問(wèn)號(hào)來(lái)在其可選返回值上進(jìn)行可選鏈?zhǔn)秸{(diào)用:

var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
testScores["Dave"]?[0] = 91
testScores["Bev"]?[0] += 1
testScores["Brian"]?[0] = 72
// "Dave" 數(shù)組現(xiàn)在是 [91, 82, 84],"Bev" 數(shù)組現(xiàn)在是 [80, 94, 81]

上面的例子中定義了一個(gè) testScores 數(shù)組,包含了兩個(gè)鍵值對(duì),把 String 類型的鍵映射到一個(gè) Int 值的數(shù)組。這個(gè)例子用可選鏈?zhǔn)秸{(diào)用把 "Dave" 數(shù)組中第一個(gè)元素設(shè)為 91,把 "Bev" 數(shù)組的第一個(gè)元素 +1,然后嘗試把 "Brian" 數(shù)組中的第一個(gè)元素設(shè)為 72。前兩個(gè)調(diào)用成功,因?yàn)?testScores 字典中包含 "Dave""Bev" 這兩個(gè)鍵。但是 testScores 字典中沒(méi)有 "Brian" 這個(gè)鍵,所以第三個(gè)調(diào)用失敗。

連接多層可選鏈?zhǔn)秸{(diào)用

可以通過(guò)連接多個(gè)可選鏈?zhǔn)秸{(diào)用在更深的模型層級(jí)中訪問(wèn)屬性、方法以及下標(biāo)。然而,多層可選鏈?zhǔn)秸{(diào)用不會(huì)增加返回值的可選層級(jí)。

也就是說(shuō):

  • 如果你訪問(wèn)的值不是可選的,可選鏈?zhǔn)秸{(diào)用將會(huì)返回可選值。
  • 如果你訪問(wèn)的值就是可選的,可選鏈?zhǔn)秸{(diào)用不會(huì)讓可選返回值變得“更可選”。

因此:

  • 通過(guò)可選鏈?zhǔn)秸{(diào)用訪問(wèn)一個(gè) Int 值,將會(huì)返回 Int?,無(wú)論使用了多少層可選鏈?zhǔn)秸{(diào)用。
  • 類似的,通過(guò)可選鏈?zhǔn)秸{(diào)用訪問(wèn) Int? 值,依舊會(huì)返回 Int? 值,并不會(huì)返回 Int??

下面的例子嘗試訪問(wèn) john 中的 residence 屬性中的 address 屬性中的 street 屬性。這里使用了兩層可選鏈?zhǔn)秸{(diào)用,residence 以及 address 都是可選值:

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.”

john.residence 現(xiàn)在包含一個(gè)有效的 Residence 實(shí)例。然而,john.residence.address 的值當(dāng)前為 nil。因此,調(diào)用 john.residence?.address?.street 會(huì)失敗。

需要注意的是,上面的例子中,street 的屬性為 String?。john.residence?.address?.street 的返回值也依然是 String?,即使已經(jīng)使用了兩層可選鏈?zhǔn)秸{(diào)用。

如果為 john.residence.address 賦值一個(gè) Address 實(shí)例,并且為 address 中的 street 屬性設(shè)置一個(gè)有效值,我們就能過(guò)通過(guò)可選鏈?zhǔn)秸{(diào)用來(lái)訪問(wèn) street 屬性:

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.”

在上面的例子中,因?yàn)?john.residence 包含一個(gè)有效的 Address 實(shí)例,所以對(duì) john.residenceaddress 屬性賦值將會(huì)成功。

在方法的可選返回值上進(jìn)行可選鏈?zhǔn)秸{(diào)用

上面的例子展示了如何在一個(gè)可選值上通過(guò)可選鏈?zhǔn)秸{(diào)用來(lái)獲取它的屬性值。我們還可以在一個(gè)可選值上通過(guò)可選鏈?zhǔn)秸{(diào)用來(lái)調(diào)用方法,并且可以根據(jù)需要繼續(xù)在方法的可選返回值上進(jìn)行可選鏈?zhǔn)秸{(diào)用。

在下面的例子中,通過(guò)可選鏈?zhǔn)秸{(diào)用來(lái)調(diào)用 AddressbuildingIdentifier() 方法。這個(gè)方法返回 String? 類型的值。如上所述,通過(guò)可選鏈?zhǔn)秸{(diào)用來(lái)調(diào)用該方法,最終的返回值依舊會(huì)是 String? 類型:

if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
    print("John's building identifier is \(buildingIdentifier).")
}
// 打印 “John's building identifier is The Larches.”

如果要在該方法的返回值上進(jìn)行可選鏈?zhǔn)秸{(diào)用,在方法的圓括號(hào)后面加上問(wèn)號(hào)即可:

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 does not begin with \"The\".")
        }
}
// 打印 “John's building identifier begins with "The".”

注意

在上面的例子中,在方法的圓括號(hào)后面加上問(wèn)號(hào)是因?yàn)槟阋?buildingIdentifier() 方法的可選返回值上進(jìn)行可選鏈?zhǔn)秸{(diào)用,而不是 buildingIdentifier() 方法本身。