這里的教程為Swift官方教程中文版。

可選鏈?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

本頁(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)制展開

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

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

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

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

首先定義兩個(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)制展開獲得這個(gè)johnresidence屬性中的numberOfRooms值,會(huì)觸發(fā)運(yùn)行時(shí)錯(cuò)誤,因?yàn)檫@時(shí)residence沒有可以展開的值:

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)用提供了另一種訪問numberOfRooms的方式,使用問號(hào)(?)來替代原來的嘆號(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后面添加問號(hào)之后,Swift 就會(huì)在residence不為nil的情況下訪問numberOfRooms。

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

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

下面這段代碼定義了四個(gè)模型類,這些例子包括多層可選鏈?zhǔn)秸{(diào)用。為了方便說明,在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還提供了訪問rooms數(shù)組的快捷方式,即提供可讀寫的下標(biāo)來訪問rooms數(shù)組中指定位置的元素。

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

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

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

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

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

下面的代碼創(chuàng)建了一個(gè)Person實(shí)例,然后像之前一樣,嘗試訪問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)?code>john.residence為nil,所以這個(gè)可選鏈?zhǔn)秸{(diào)用依舊會(huì)像先前一樣失敗。

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

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

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

上面代碼中的賦值過程是可選鏈?zhǔn)秸{(diào)用的一部分,這意味著可選鏈?zhǔn)秸{(diào)用失敗時(shí),等號(hào)右側(cè)的代碼不會(huì)被執(zhí)行。對(duì)于上面的代碼來說,很難驗(yàn)證這一點(diǎn),因?yàn)橄襁@樣賦值一個(gè)常量沒有任何副作用。下面的代碼完成了同樣的事情,但是它使用一個(gè)函數(shù)來創(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()

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

通過可選鏈?zhǔn)秸{(diào)用調(diào)用方法

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

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

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

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

如果在可選值上通過可選鏈?zhǔn)秸{(diào)用來調(diào)用這個(gè)方法,該方法的返回類型會(huì)是Void?,而不是Void,因?yàn)橥ㄟ^可選鏈?zhǔn)秸{(diào)用得到的返回值都是可選的。這樣我們就可以使用if語(yǔ)句來判斷能否成功調(diào)用printNumberOfRooms()方法,即使方法本身沒有定義返回值。通過判斷返回值是否為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ù)此判斷通過可選鏈?zhǔn)秸{(diào)用為屬性賦值是否成功。在上面的通過可選鏈?zhǔn)秸{(diào)用訪問屬性的例子中,我們嘗試給john.residence中的address屬性賦值,即使residencenil。通過可選鏈?zhǔn)秸{(diào)用給屬性賦值會(huì)返回Void?,通過判斷返回值是否為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.”

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

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

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

下面這個(gè)例子用下標(biāo)訪問john.residence屬性存儲(chǔ)的Residence實(shí)例的rooms數(shù)組中的第一個(gè)房間的名稱,因?yàn)?code>john.residence為nil,所以下標(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è)例子中,問號(hào)直接放在john.residence的后面,并且在方括號(hào)的前面,因?yàn)?code>john.residence是可選值。

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

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

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

如果你創(chuàng)建一個(gè)Residence實(shí)例,并為其rooms數(shù)組添加一些Room實(shí)例,然后將Residence實(shí)例賦值給john.residence,那就可以通過可選鏈和下標(biāo)來訪問數(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.”

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

如果下標(biāo)返回可選類型值,比如 Swift 中Dictionary類型的鍵的下標(biāo),可以在下標(biāo)的結(jié)尾括號(hào)后面放一個(gè)問號(hào)來在其可選返回值上進(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)?code>testScores字典中包含"Dave""Bev"這兩個(gè)鍵。但是testScores字典中沒有"Brian"這個(gè)鍵,所以第三個(gè)調(diào)用失敗。

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

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

也就是說:

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

因此:

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

下面的例子嘗試訪問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è)有效值,我們就能過通過可選鏈?zhǔn)秸{(diào)用來訪問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)?code>john.residence包含一個(gè)有效的Address實(shí)例,所以對(duì)john.residenceaddress屬性賦值將會(huì)成功。

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

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

在下面的例子中,通過可選鏈?zhǔn)秸{(diào)用來調(diào)用AddressbuildingIdentifier()方法。這個(gè)方法返回String?類型的值。如上所述,通過可選鏈?zhǔn)秸{(diào)用來調(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)后面加上問號(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)后面加上問號(hào)是因?yàn)槟阋?code>buildingIdentifier()方法的可選返回值上進(jìn)行可選鏈?zhǔn)秸{(diào)用,而不是方法本身。

? 自動(dòng)引用計(jì)數(shù) 錯(cuò)誤處理 ?
?