★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(shanqingyongzhi)
➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/)
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:https://www.cnblogs.com/strengthen/p/9739421.html
➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
可选链接是一个查询和调用当前可选的可选项的属性,方法和下标的过程nil
。如果optional包含值,则属性,方法或下标调用成功; 如果是可选的nil
,则返回属性,方法或下标调用nil
。多个查询可以链接在一起,如果链中的任何链接,整个链都会正常失败nil
。
注意
Swift中的可选链接类似于nil
Objective-C中的消息传递,但其方式适用于任何类型,并且可以检查其成功与否。
可选链接作为强制解包的替代方法
您可以通过?
在可选值之后放置一个问号()来指定可选链接,如果可选值为非,则在该值上调用属性,方法或下标nil
。这与!
在可选值之后放置感叹号()以强制展开其值非常相似。主要区别在于可选链接在可选项时优雅地失败nil
,而强制解包在可选项时触发运行时错误nil
。
要反映可以在nil
值上调用可选链接的事实,可选链接调用的结果始终是可选值,即使要查询的属性,方法或下标返回非可选值。您可以使用此可选返回值来检查可选链接调用是否成功(返回的可选项包含值),或者由于nil
链中的值(返回的可选值为nil
)而未成功。
具体来说,可选链接调用的结果与预期返回值的类型相同,但包含在可选中。通常返回an的属性Int
将Int?
在通过可选链接访问时返回。
接下来的几个代码片段演示了可选链接与强制解包的区别,并使您能够检查是否成功。
首先,调用Person
和Residence
定义了两个类:
- class Person {
- var residence: Residence?
- }
- class Residence {
- var numberOfRooms = 1
- }
Residence
实例有一个Int
名为的属性numberOfRooms
,默认值为1
。Person
实例具有residence
类型的可选属性Residence?
。
如果您创建一个新Person
实例,则其residence
属性默认初始化为nil
,因为它是可选的。在下面的代码,john
具有residence
的属性值nil
:
- let john = Person()
如果您尝试访问numberOfRooms
此人的属性residence
,通过在其后放置感叹号residence
强制展开其值,则会触发运行时错误,因为没有residence
值要解包:
- let roomCount = john.residence!.numberOfRooms
- // this triggers a runtime error
如果john.residence
具有非nil
值,则上面的代码将成功,并将设置roomCount
为Int
包含适当房间数的值。然而,当该代码始终触发运行时错误residence
是nil
,如上述所示。
可选链接提供了一种访问其值的替代方法numberOfRooms
。要使用可选链接,请使用问号代替感叹号:
- if let roomCount = john.residence?.numberOfRooms {
- print("John's residence has \(roomCount) room(s).")
- } else {
- print("Unable to retrieve the number of rooms.")
- }
- // Prints "Unable to retrieve the number of rooms."
这告诉Swift在可选residence
属性上“链” 并检索numberOfRooms
if residence
exists 的值。
由于访问尝试numberOfRooms
可能会失败,因此可选的链接尝试返回值类型Int?
或“可选Int
”。如果residence
是nil
,如上面的例子中,这个可选的Int
也将是nil
,反映事实,这是不可能的访问numberOfRooms
。可选的Int
是通过可选的结合访问以解开的整数并分配非可选值到roomCount
变量。
请注意,即使这numberOfRooms
是非可选的,也是如此Int
。通过可选链查询它的事实意味着调用numberOfRooms
将始终返回Int?
而不是Int
。
您可以将Residence
实例分配给john.residence
,以便它不再具有nil
值:
- john.residence = Residence()
john.residence
现在包含一个实际的Residence
实例,而不是nil
。如果您尝试使用numberOfRooms
与以前相同的可选链接进行访问,它现在将返回Int?
包含默认numberOfRooms
值的1
:
- if let roomCount = john.residence?.numberOfRooms {
- print("John's residence has \(roomCount) room(s).")
- } else {
- print("Unable to retrieve the number of rooms.")
- }
- // Prints "John's residence has 1 room(s)."
为可选链接定义模型类
您可以使用可选链接来调用多个级别的属性,方法和下标。这使您可以深入查看相互关联类型的复杂模型中的子属性,并检查是否可以访问这些子属性上的属性,方法和下标。
下面的代码片段定义了四个模型类,用于几个后续示例,包括多级可选链接的示例。这些类在所述展开Person
和Residence
通过添加模型从上方Room
和Address
类,具有相关联的属性,方法,和下标。
该Person
班以同样的方式前的定义:
- class Person {
- var residence: Residence?
- }
这Residence
门课比以前更复杂。这一次,Residence
该类定义了一个名为的变量属性rooms
,该属性使用类型为空的数组进行初始化[Room]
:
- 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?
- }
因为此版本的Residence
存储是一个Room
实例数组,所以它的numberOfRooms
属性实现为计算属性,而不是存储属性。computed numberOfRooms
属性只是count
从rooms
数组中返回属性的值。
作为访问其rooms
数组的快捷方式,此版本Residence
提供了一个读写下标,可以在rooms
阵列中的请求索引处提供对房间的访问。
此版本Residence
还提供了一种名为的方法printNumberOfRooms
,它只是打印住宅中的房间数量。
最后,Residence
定义一个名为的可选属性address
,其类型为Address?
。Address
此属性的类类型定义如下。
在Room
用于类rooms
阵列是简单的类与一种属性调用name
,以及一个初始值设定到该属性设置为一个合适的房间名称:
- 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 let buildingNumber = buildingNumber, let street = street {
- return "\(buildingNumber) \(street)"
- } else if buildingName != nil {
- return buildingName
- } else {
- return nil
- }
- }
- }
所述Address
类还提供了一个名为方法buildingIdentifier()
,其具有的返回类型String?
。此方法检查地址的属性,buildingName
如果有值,则返回buildingNumber
,street
如果两者都有值,则返回连接,nil
否则返回。
通过可选链接访问属性
如可选链接作为强制解包的替代方案所示,您可以使用可选链接访问可选值的属性,并检查该属性访问是否成功。
使用上面定义的类来创建新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.")
- }
- // Prints "Unable to retrieve the number of rooms."
由于john.residence
是nil
,这种可选的链接调用以同样的方式和以前失败。
您还可以尝试通过可选链接设置属性的值:
- let someAddress = Address()
- someAddress.buildingNumber = "29"
- someAddress.street = "Acacia Road"
- john.residence?.address = someAddress
在此示例中,设置address
属性的尝试john.residence
将失败,因为john.residence
当前nil
。
赋值是可选链接的一部分,这意味着不会对=
运算符右侧的代码进行求值。在前面的示例中,不容易看到someAddress
永远不会评估,因为访问常量没有任何副作用。下面的列表执行相同的分配,但它使用一个函数来创建地址。该函数在返回值之前打印“函数被调用”,这样可以查看是否=
评估了操作符的右侧。
- func createAddress() -> Address {
- print("Function was called.")
- let someAddress = Address()
- someAddress.buildingNumber = "29"
- someAddress.street = "Acacia Road"
- return someAddress
- }
- john.residence?.address = createAddress()
您可以判断该createAddress()
函数未被调用,因为没有打印任何内容。
通过可选链接调用方法
您可以使用可选链接在可选值上调用方法,并检查该方法调用是否成功。即使该方法没有定义返回值,也可以执行此操作。
在printNumberOfRooms()
对方法Residence
类打印的当前值numberOfRooms
。以下是该方法的外观:
- func printNumberOfRooms() {
- print("The number of rooms is \(numberOfRooms)")
- }
此方法未指定返回类型。但是,没有返回类型的函数和方法具有隐式返回类型Void
,如函数无返回值中所述。这意味着它们返回值()
或空元组。
如果在带有可选链接的可选值上调用此方法,则方法的返回类型将Void?
不是Void
,因为通过可选链接调用时返回值始终是可选类型。这使您可以使用if
语句来检查是否可以调用该printNumberOfRooms()
方法,即使该方法本身不定义返回值。比较来自printNumberOfRooms
调用的返回值,nil
以查看方法调用是否成功:
- 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.")
- }
- // Prints "It was not possible to print the number of rooms."
如果您尝试通过可选链接设置属性,情况也是如此。通过可选链接访问属性中的上述示例尝试为其设置address
值john.residence
,即使该residence
属性为nil
。任何通过可选链接设置属性的尝试都会返回type值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.")
- }
- // Prints "It was not possible to set the address."
通过可选链接访问下标
您可以使用可选链接尝试从可选值的下标中检索和设置值,并检查该下标调用是否成功。
注意
通过可选链接访问可选值的下标时,将问号放在下标括号之前,而不是之后。可选的链接问号始终紧跟在表达式的可选部分之后。
下面的示例尝试使用类上定义的下标检索属性rooms
数组中第一个房间的名称。因为当前,下标调用失败:john.residence
Residence
john.residence
nil
- if let firstRoomName = john.residence?[0].name {
- print("The first room name is \(firstRoomName).")
- } else {
- print("Unable to retrieve the first room name.")
- }
- // Prints "Unable to retrieve the first room name."
此下标调用中的可选链接问号位于下john.residence
标括号之后,因为john.residence
是可选值,在该值上可选择链接。
同样,您可以尝试通过带有可选链接的下标设置新值:
- john.residence?[0] = Room(name: "Bathroom")
此下标设置尝试也失败,因为residence
当前nil
。
如果您创建并分配实际Residence
实例john.residence
,并Room
在其rooms
数组中包含一个或多个实例,则可以使用Residence
下标rooms
通过可选链接访问数组中的实际项:
- 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.")
- }
- // Prints "The first room name is Living Room."
访问可选类型的下标
如果下标返回一个可选类型的值 - 例如Swift Dictionary
类型的键下标 - 在下标的右括号后面加一个问号,以链接其可选的返回值:
- var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
- testScores["Dave"]?[0] = 91
- testScores["Bev"]?[0] += 1
- testScores["Brian"]?[0] = 72
- // the "Dave" array is now [91, 82, 84] and the "Bev" array is now [80, 94, 81]
上面的示例定义了一个名为的字典testScores
,它包含两个将键映射String
到Int
值数组的键值对。该示例使用可选链接将"Dave"
数组中的第一个项目设置为91
; 增加"Bev"
数组中的第一项1
; 并尝试在数组中为键的第一项设置"Brian"
。前两个调用成功,因为testScores
字典包含"Dave"
和的键"Bev"
。第三个调用失败,因为testScores
字典不包含密钥"Brian"
。
链接多级链接
您可以将多个级别的可选链接链接在一起,以深入查看模型中更深层次的属性,方法和下标。但是,多级可选链接不会为返回值添加更多级别的可选性。
换一种方式:
- 如果您要检索的类型不是可选的,则由于可选链接,它将成为可选类型。
- 如果您尝试检索的类型已经是可选的,则由于链接,它不会变得更加可选。
因此:
- 如果尝试
Int
通过可选链接检索值,Int?
则无论使用多少级别的链接,都始终返回a。 - 同样,如果您尝试
Int?
通过可选链接检索值,Int?
则无论使用多少级别的链接,都会始终返回a。
下面的示例尝试访问street
属性的address
属性的residence
属性john
。这里有两个级别的可选链接,用于链接residence
和address
属性,两者都是可选类型:
- if let johnsStreet = john.residence?.address?.street {
- print("John's street name is \(johnsStreet).")
- } else {
- print("Unable to retrieve the address.")
- }
- // Prints "Unable to retrieve the address."
当前值john.residence
包含有效Residence
实例。但是,john.residence.address
目前的价值nil
。因此,调用john.residence?.address?.street
失败。
请注意,在上面的示例中,您尝试检索street
属性的值。这个属性的类型是String?
。因此,返回值john.residence?.address?.street
也是String?
,即使除了属性的基础可选类型之外还应用了两个级别的可选链接。
如果将实际Address
实例设置为for的值john.residence.address
,并为地址的street
属性设置实际值,则可以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.")
- }
- // Prints "John's street name is Laurel Street."
在此示例中,尝试设置address
属性john.residence
将成功,因为当前值john.residence
包含有效Residence
实例。
使用可选返回值链接方法
前面的示例演示如何通过可选链接检索可选类型的属性的值。您还可以使用可选链接来调用返回可选类型值的方法,并在需要时链接该方法的返回值。
下面的示例通过可选链接调用Address
类的buildingIdentifier()
方法。此方法返回type的值String?
。如上所述,可选链接后此方法调用的最终返回类型还包括String?
:
- if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
- print("John's building identifier is \(buildingIdentifier).")
- }
- // Prints "John's building identifier is The Larches."
如果你想在这个方法的返回值进行进一步的可选链接,将链接可选问号后,该方法的括号:
- 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\".")
- }
- }
- // Prints "John's building identifier begins with "The"."
注意
在上面的例子中,您将可选链接问号后的括号内,因为你要串联上可选的值是buildingIdentifier()
方法的返回值,而不是buildingIdentifier()
方法本身。