1.0 翻譯:Jasonbroker 校對:numbbbbb, stanzhai
2.0 翻譯+校對:lyojo
2.1 校對:shanks,2015-10-31
2.2 翻譯+校對:SketchK 2016-05-15
3.0.1,shanks,2016-11-13
本頁包含內(nèi)容:
可選鏈?zhǔn)秸{(diào)用是一種可以在當(dāng)前值可能為nil
的可選值上請求和調(diào)用屬性、方法及下標(biāo)的方法。如果可選值有值,那么調(diào)用就會成功;如果可選值是nil
,那么調(diào)用將返回nil
。多個調(diào)用可以連接在一起形成一個調(diào)用鏈,如果其中任何一個節(jié)點為nil
,整個調(diào)用鏈都會失敗,即返回nil
。
注意
Swift 的可選鏈?zhǔn)秸{(diào)用和 Objective-C 中向nil
發(fā)送消息有些相像,但是 Swift 的可選鏈?zhǔn)秸{(diào)用可以應(yīng)用于任意類型,并且能檢查調(diào)用是否成功。
通過在想調(diào)用的屬性、方法、或下標(biāo)的可選值后面放一個問號(?
),可以定義一個可選鏈。這一點很像在可選值后面放一個嘆號(!
)來強(qiáng)制展開它的值。它們的主要區(qū)別在于當(dāng)可選值為空時可選鏈?zhǔn)秸{(diào)用只會調(diào)用失敗,然而強(qiáng)制展開將會觸發(fā)運(yùn)行時錯誤。
為了反映可選鏈?zhǔn)秸{(diào)用可以在空值(nil
)上調(diào)用的事實,不論這個調(diào)用的屬性、方法及下標(biāo)返回的值是不是可選值,它的返回結(jié)果都是一個可選值。你可以利用這個返回值來判斷你的可選鏈?zhǔn)秸{(diào)用是否調(diào)用成功,如果調(diào)用有返回值則說明調(diào)用成功,返回nil
則說明調(diào)用失敗。
特別地,可選鏈?zhǔn)秸{(diào)用的返回結(jié)果與原本的返回結(jié)果具有相同的類型,但是被包裝成了一個可選值。例如,使用可選鏈?zhǔn)秸{(diào)用訪問屬性,當(dāng)可選鏈?zhǔn)秸{(diào)用成功時,如果屬性原本的返回結(jié)果是Int
類型,則會變?yōu)?code>Int?類型。
下面幾段代碼將解釋可選鏈?zhǔn)秸{(diào)用和強(qiáng)制展開的不同。
首先定義兩個類Person
和Residence
:
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}
Residence
有一個Int
類型的屬性numberOfRooms
,其默認(rèn)值為1
。Person
具有一個可選的residence
屬性,其類型為Residence?
。
假如你創(chuàng)建了一個新的Person
實例,它的residence
屬性由于是是可選型而將初始化為nil
,在下面的代碼中,john
有一個值為nil
的residence
屬性:
let john = Person()
如果使用嘆號(!
)強(qiáng)制展開獲得這個john
的residence
屬性中的numberOfRooms
值,會觸發(fā)運(yùn)行時錯誤,因為這時residence
沒有可以展開的值:
let roomCount = john.residence!.numberOfRooms
// 這會引發(fā)運(yùn)行時錯誤
john.residence
為非nil
值的時候,上面的調(diào)用會成功,并且把roomCount
設(shè)置為Int
類型的房間數(shù)量。正如上面提到的,當(dāng)residence
為nil
的時候上面這段代碼會觸發(fā)運(yùn)行時錯誤。
可選鏈?zhǔn)秸{(diào)用提供了另一種訪問numberOfRooms
的方式,使用問號(?
)來替代原來的嘆號(!
):
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
后面添加問號之后,Swift 就會在residence
不為nil
的情況下訪問numberOfRooms
。
因為訪問numberOfRooms
有可能失敗,可選鏈?zhǔn)秸{(diào)用會返回Int?
類型,或稱為“可選的 Int
”。如上例所示,當(dāng)residence
為nil
的時候,可選的Int
將會為nil
,表明無法訪問numberOfRooms
。訪問成功時,可選的Int
值會通過可選綁定展開,并賦值給非可選類型的roomCount
常量。
要注意的是,即使numberOfRooms
是非可選的Int
時,這一點也成立。只要使用可選鏈?zhǔn)秸{(diào)用就意味著numberOfRooms
會返回一個Int?
而不是Int
。
可以將一個Residence
的實例賦給john.residence
,這樣它就不再是nil
了:
john.residence = Residence()
john.residence
現(xiàn)在包含一個實際的Residence
實例,而不再是nil
。如果你試圖使用先前的可選鏈?zhǔn)秸{(diào)用訪問numberOfRooms
,它現(xiàn)在將返回值為1
的Int?
類型的值:
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)用可以調(diào)用多層屬性、方法和下標(biāo)。這樣可以在復(fù)雜的模型中向下訪問各種子屬性,并且判斷能否訪問子屬性的屬性、方法或下標(biāo)。
下面這段代碼定義了四個模型類,這些例子包括多層可選鏈?zhǔn)秸{(diào)用。為了方便說明,在Person
和Residence
的基礎(chǔ)上增加了Room
類和Address
類,以及相關(guān)的屬性、方法以及下標(biāo)。
Person
類的定義基本保持不變:
class Person {
var residence: Residence?
}
Residence
類比之前復(fù)雜些,增加了一個名為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
有了一個存儲Room
實例的數(shù)組,numberOfRooms
屬性被實現(xiàn)為計算型屬性,而不是存儲型屬性。numberOfRooms
屬性簡單地返回rooms
數(shù)組的count
屬性的值。
Residence
還提供了訪問rooms
數(shù)組的快捷方式,即提供可讀寫的下標(biāo)來訪問rooms
數(shù)組中指定位置的元素。
此外,Residence
還提供了printNumberOfRooms
方法,這個方法的作用是打印numberOfRooms
的值。
最后,Residence
還定義了一個可選屬性address
,其類型為Address?
。Address
類的定義在下面會說明。
Room
類是一個簡單類,其實例被存儲在rooms
數(shù)組中。該類只包含一個屬性name
,以及一個用于將該屬性設(shè)置為適當(dāng)?shù)姆块g名的初始化函數(shù):
class Room {
let name: String
init(name: String) { self.name = name }
}
最后一個類是Address
,這個類有三個String?
類型的可選屬性。buildingName
以及buildingNumber
屬性分別表示某個大廈的名稱和號碼,第三個屬性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
。或者,如果buildingNumber
和street
均有值則返回buildingNumber
。否則,返回nil
。
正如使用可選鏈?zhǔn)秸{(diào)用代替強(qiáng)制展開中所述,可以通過可選鏈?zhǔn)秸{(diào)用在一個可選值上訪問它的屬性,并判斷訪問是否成功。
下面的代碼創(chuàng)建了一個Person
實例,然后像之前一樣,嘗試訪問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.”
因為john.residence
為nil
,所以這個可選鏈?zhǔn)秸{(diào)用依舊會像先前一樣失敗。
還可以通過可選鏈?zhǔn)秸{(diào)用來設(shè)置屬性值:
let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
john.residence?.address = someAddress
在這個例子中,通過john.residence
來設(shè)定address
屬性也會失敗,因為john.residence
當(dāng)前為nil
。
上面代碼中的賦值過程是可選鏈?zhǔn)秸{(diào)用的一部分,這意味著可選鏈?zhǔn)秸{(diào)用失敗時,等號右側(cè)的代碼不會被執(zhí)行。對于上面的代碼來說,很難驗證這一點,因為像這樣賦值一個常量沒有任何副作用。下面的代碼完成了同樣的事情,但是它使用一個函數(shù)來創(chuàng)建Address
實例,然后將該實例返回用于賦值。該函數(shù)會在返回前打印“Function was called”,這使你能驗證等號右側(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)用方法,并判斷是否調(diào)用成功,即使這個方法沒有返回值。
Residence
類中的printNumberOfRooms()
方法打印當(dāng)前的numberOfRooms
值,如下所示:
func printNumberOfRooms() {
print("The number of rooms is \(numberOfRooms)")
}
這個方法沒有返回值。然而,沒有返回值的方法具有隱式的返回類型Void
,如無返回值函數(shù)中所述。這意味著沒有返回值的方法也會返回()
,或者說空的元組。
如果在可選值上通過可選鏈?zhǔn)秸{(diào)用來調(diào)用這個方法,該方法的返回類型會是Void?
,而不是Void
,因為通過可選鏈?zhǔn)秸{(diào)用得到的返回值都是可選的。這樣我們就可以使用if
語句來判斷能否成功調(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
屬性賦值,即使residence
為nil
。通過可選鏈?zhǔn)秸{(diào)用給屬性賦值會返回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),并且判斷下標(biāo)調(diào)用是否成功。
注意
通過可選鏈?zhǔn)秸{(diào)用訪問可選值的下標(biāo)時,應(yīng)該將問號放在下標(biāo)方括號的前面而不是后面??蛇x鏈?zhǔn)秸{(diào)用的問號一般直接跟在可選表達(dá)式的后面。
下面這個例子用下標(biāo)訪問john.residence
屬性存儲的Residence
實例的rooms
數(shù)組中的第一個房間的名稱,因為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.”
在這個例子中,問號直接放在john.residence
的后面,并且在方括號的前面,因為john.residence
是可選值。
類似的,可以通過下標(biāo),用可選鏈?zhǔn)秸{(diào)用來賦值:
john.residence?[0] = Room(name: "Bathroom")
這次賦值同樣會失敗,因為residence
目前是nil
。
如果你創(chuàng)建一個Residence
實例,并為其rooms
數(shù)組添加一些Room
實例,然后將Residence
實例賦值給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)返回可選類型值,比如 Swift 中Dictionary
類型的鍵的下標(biāo),可以在下標(biāo)的結(jié)尾括號后面放一個問號來在其可選返回值上進(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]
上面的例子中定義了一個testScores
數(shù)組,包含了兩個鍵值對,把String
類型的鍵映射到一個Int
值的數(shù)組。這個例子用可選鏈?zhǔn)秸{(diào)用把"Dave"
數(shù)組中第一個元素設(shè)為91
,把"Bev"
數(shù)組的第一個元素+1
,然后嘗試把"Brian"
數(shù)組中的第一個元素設(shè)為72
。前兩個調(diào)用成功,因為testScores
字典中包含"Dave"
和"Bev"
這兩個鍵。但是testScores
字典中沒有"Brian"
這個鍵,所以第三個調(diào)用失敗。
可以通過連接多個可選鏈?zhǔn)秸{(diào)用在更深的模型層級中訪問屬性、方法以及下標(biāo)。然而,多層可選鏈?zhǔn)秸{(diào)用不會增加返回值的可選層級。
也就是說:
因此:
Int
值,將會返回Int?
,無論使用了多少層可選鏈?zhǔn)秸{(diào)用。Int?
值,依舊會返回Int?
值,并不會返回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)在包含一個有效的Residence
實例。然而,john.residence.address
的值當(dāng)前為nil
。因此,調(diào)用john.residence?.address?.street
會失敗。
需要注意的是,上面的例子中,street
的屬性為String?
。john.residence?.address?.street
的返回值也依然是String?
,即使已經(jīng)使用了兩層可選鏈?zhǔn)秸{(diào)用。
如果為john.residence.address
賦值一個Address
實例,并且為address
中的street
屬性設(shè)置一個有效值,我們就能過通過可選鏈?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.”
在上面的例子中,因為john.residence
包含一個有效的Residence
實例,所以對john.residence
的address
屬性賦值將會成功。
上面的例子展示了如何在一個可選值上通過可選鏈?zhǔn)秸{(diào)用來獲取它的屬性值。我們還可以在一個可選值上通過可選鏈?zhǔn)秸{(diào)用來調(diào)用方法,并且可以根據(jù)需要繼續(xù)在方法的可選返回值上進(jìn)行可選鏈?zhǔn)秸{(diào)用。
在下面的例子中,通過可選鏈?zhǔn)秸{(diào)用來調(diào)用Address
的buildingIdentifier()
方法。這個方法返回String?
類型的值。如上所述,通過可選鏈?zhǔn)秸{(diào)用來調(diào)用該方法,最終的返回值依舊會是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)用,在方法的圓括號后面加上問號即可:
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".”
注意
在上面的例子中,在方法的圓括號后面加上問號是因為你要在buildingIdentifier()
方法的可選返回值上進(jìn)行可選鏈?zhǔn)秸{(diào)用,而不是方法本身。