Swift5.4 语言指南,十八 可选链接

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

➤微信公众号:山青咏芝(shanqingyongzhi)

➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/

➤GitHub地址:https://github.com/strengthen/LeetCode

➤原文地址:https://www.cnblogs.com/strengthen/p/9739421.html

➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。

➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

热烈欢迎,请直接点击!!!

进入博主App Store主页,下载使用各个作品!!!

注:博主将坚持每月上线一个新app!!!

可选链接是用于查询和调用当前可能是的可选属性,方法和下标的过程nil。如果可选包含值,则属性,方法或下标调用成功;否则,调用成功。如果可选参数为nil,则属性,方法或下标调用返回nil。可以将多个查询链接在一起,如果链中的任何链接为,则整个链都会正常失败nil

笔记

Swift中的可选链接类似于nilObjective-C中的消息传递,但是它适用于任何类型,并且可以检查成功或失败。

可选链接作为强制展开的替代方法

您可以通过在?要调用其属性,方法或下标的可选值之后放置问号()来指定可选链,如果可选值不是- nil。这非常类似于将感叹号(!)放在可选值之后以强制展开其值。主要区别在于,当可选选项为时,可选链接会优雅地失败nil,而当可选选项为时,强制展开会触发运行时错误nil

为了反映可以在nil值上调用可选链接的事实,即使您要查询的属性,方法或下标返回非可选值,可选链接调用的结果也始终是可选值。您可以使用此可选返回值来检查可选链接调用是否成功(返回的可选值包含一个值),或者由于nil链中的值而失败(返回的可选值是nil)。

具体来说,可选链接调用的结果与期望的返回值具有相同的类型,但包装在可选中。通过可选链接访问时,通常返回an的属性Int将返回a Int?

接下来的几个代码片段演示了可选链接与强制展开如何不同,并使您能够检查是否成功。

首先,定义了两个类PersonResidence分别定义为和:

  1. class Person {
  2. var residence: Residence?
  3. }
  4. class Residence {
  5. var numberOfRooms = 1
  6. }

Residence实例有一个Int名为的属性numberOfRooms,默认值为1Person实例具有residence类型的可选属性Residence?

如果创建一个新Person实例,则其residence属性默认为nil,因为它是可选的。在下面的代码中,johnresidence属性值为nil

  1. let john = Person()

如果您尝试访问numberOfRooms此人的属性residence,则通过在其后residence强制放置一个感叹号来对其值进行解包,则会触发运行时错误,因为没有residence要解包的值:

  1. let roomCount = john.residence!.numberOfRooms
  2. // this triggers a runtime error

上面的代码在john.residence为非nil值时成功,并将设置roomCountInt包含适当数量房间的值。但是,如上所述,此代码始终会在residenceis时触发运行时错误nil

可选链接为访问的值提供了另一种方法numberOfRooms。要使用可选链接,请使用问号代替感叹号:

  1. if let roomCount = john.residence?.numberOfRooms {
  2. print("John's residence has \(roomCount) room(s).")
  3. } else {
  4. print("Unable to retrieve the number of rooms.")
  5. }
  6. // Prints "Unable to retrieve the number of rooms."

这告诉Swift将“链”到可选residence属性上,并检索numberOfRoomsifresidence存在的值。

由于尝试访问numberOfRooms有可能失败,因此可选的链接尝试将返回typeInt?或“ optional Int”的值。如上例所示,当residence为is时nil,此可选参数Int也将为nil,以反映无法访问的事实numberOfRooms。可选的Int是通过可选的结合解开的整数并分配非可选值到访问roomCount恒定。

请注意,即使这numberOfRooms是非可选的,也是如此Int。通过可选链查询它的事实意味着,对的调用numberOfRooms将始终返回Int?而不是Int

您可以将Residence实例分配给john.residence,以使其不再具有nil值:

  1. john.residence = Residence()

john.residence现在包含一个实际Residence实例,而不是nil。如果您尝试使用numberOfRooms与以前相同的可选链接进行访问,则它将返回一个Int?包含默认numberOfRooms值的1

  1. if let roomCount = john.residence?.numberOfRooms {
  2. print("John's residence has \(roomCount) room(s).")
  3. } else {
  4. print("Unable to retrieve the number of rooms.")
  5. }
  6. // Prints "John's residence has 1 room(s)."

定义可选链接的模型类

您可以将可选链接与深度超过一级的属性,方法和下标一起使用。这使您可以深入研究相互关联类型的复杂模型中的子属性,并检查是否可以访问这些子属性上的属性,方法和下标。

下面的代码段定义了四个模型类,以供随后的几个示例中使用,包括多级可选链接的示例。这些类通过添加和类以及相关的属性,方法和下标,从上面扩展了PersonandResidence模型。RoomAddress

Person班以同样的方式前的定义:

  1. class Person {
  2. var residence: Residence?
  3. }

Residence班是比以前更加复杂。这次,Residence该类定义了一个名为的变量属性rooms,该属性用一个类型为空的数组初始化[Room]

  1. class Residence {
  2. var rooms = [Room]()
  3. var numberOfRooms: Int {
  4. return rooms.count
  5. }
  6. subscript(i: Int) -> Room {
  7. get {
  8. return rooms[i]
  9. }
  10. set {
  11. rooms[i] = newValue
  12. }
  13. }
  14. func printNumberOfRooms() {
  15. print("The number of rooms is \(numberOfRooms)")
  16. }
  17. var address: Address?
  18. }

因为此版本的Residence存储Room实例数组,所以其numberOfRooms属性实现为计算属性,而不是存储属性。计算的numberOfRooms属性只是countrooms数组中返回该属性的值。

作为访问其rooms数组的捷径,此版本Residence提供了一个读写下标,该下标提供对rooms数组中请求的索引处的房间的访问。

此版本的版本Residence还提供了一种称为的方法printNumberOfRooms,该方法可以简单地打印住宅中的房间数量。

最后,Residence定义一个名为的可选属性address,类型为Address?Address此属性的类类型在下面定义。

Room用于类rooms阵列是简单的类与一种属性调用name,以及一个初始值设定到该属性设置为一个合适的房间名称:

  1. class Room {
  2. let name: String
  3. init(name: String) { self.name = name }
  4. }

此模型中的最后一个类称为Address。此类具有type的三个可选属性String?。前两个属性buildingNamebuildingNumber是标识特定建筑物作为地址一部分的替代方法。第三个属性,street用于为该地址命名街道:

  1. class Address {
  2. var buildingName: String?
  3. var buildingNumber: String?
  4. var street: String?
  5. func buildingIdentifier() -> String? {
  6. if let buildingNumber = buildingNumber, let street = street {
  7. return "\(buildingNumber) \(street)"
  8. } else if buildingName != nil {
  9. return buildingName
  10. } else {
  11. return nil
  12. }
  13. }
  14. }

所述Address类还提供了一个名为方法buildingIdentifier(),其具有的返回类型String?。此方法检查所述地址的属性,并返回buildingName,如果它有一个值,或buildingNumber与级联street如果两者都具有值,或nil以其他方式。

通过可选链接访问属性

如可选链作为强制解包的替代中所示,您可以使用可选链来访问可选值上的属性,并检查该属性访问是否成功。

使用上面定义的类创建一个新Person实例,并尝试numberOfRooms像以前一样访问其属性:

  1. let john = Person()
  2. if let roomCount = john.residence?.numberOfRooms {
  3. print("John's residence has \(roomCount) room(s).")
  4. } else {
  5. print("Unable to retrieve the number of rooms.")
  6. }
  7. // Prints "Unable to retrieve the number of rooms."

因为john.residencenil,所以此可选链接调用以与以前相同的方式失败。

您也可以尝试通过可选的链接设置属性的值:

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

在此示例中,设置的address属性的尝试john.residence将失败,因为john.residence当前为nil

分配是可选链接的一部分,这意味着不会=评估运算符右侧的任何代码。在前面的示例中,很难看到它someAddress从未被评估过,因为访问常量没有任何副作用。下面的清单执行相同的分配,但是它使用一个函数来创建地址。该函数在返回值之前打印“调用了函数”,这使您可以查看是否对=运算符的右侧进行了评估。

  1. func createAddress() -> Address {
  2. print("Function was called.")
  3. let someAddress = Address()
  4. someAddress.buildingNumber = "29"
  5. someAddress.street = "Acacia Road"
  6. return someAddress
  7. }
  8. john.residence?.address = createAddress()

您可以说createAddress()没有调用该函数,因为没有打印任何内容。

通过可选链接调用方法

您可以使用可选链接来对可选值调用方法,并检查该方法调用是否成功。即使该方法未定义返回值,也可以执行此操作。

printNumberOfRooms()对方法Residence类打印的当前值numberOfRooms。该方法的外观如下:

  1. func printNumberOfRooms() {
  2. print("The number of rooms is \(numberOfRooms)")
  3. }

此方法未指定返回类型。但是,没有返回类型的函数和方法具有隐式返回类型Void,如无返回值的函数中所述。这意味着它们返回的值为(),或者为空的元组。

如果通过可选链对可选值调用此方法,则该方法的返回类型将为Void?,而不是Void,因为通过可选链调用时,返回值始终为可选类型。这使您可以使用if语句检查是否可以调用该printNumberOfRooms()方法,即使该方法本身未定义返回值。将printNumberOfRooms调用的返回值与之进行比较,nil以查看方法调用是否成功:

  1. if john.residence?.printNumberOfRooms() != nil {
  2. print("It was possible to print the number of rooms.")
  3. } else {
  4. print("It was not possible to print the number of rooms.")
  5. }
  6. // Prints "It was not possible to print the number of rooms."

如果尝试通过可选链接设置属性,则情况也是如此。上面的“通过可选链接访问属性”中的示例尝试为设置一个addressjohn.residence,即使该residence属性为nil。通过可选链接设置属性的任何尝试都会返回type的值Void?,这使您可以与之进行比较nil以查看是否成功设置了该属性:

  1. if (john.residence?.address = someAddress) != nil {
  2. print("It was possible to set the address.")
  3. } else {
  4. print("It was not possible to set the address.")
  5. }
  6. // Prints "It was not possible to set the address."

通过可选链接访问下标

您可以使用可选链接尝试从下标中检索和设置可选值上的值,并检查该下标调用是否成功。

笔记

通过可选链访问可选值上的下标时,将问号放在下标的括号之前,而不是之后。可选链接问号总是紧接在表达式的可选部分之后。

下面的示例尝试使用在类上定义的下标检索属性rooms数组中第一个房间的名称。因为当前为,所以下标调用失败:john.residenceResidencejohn.residencenil

  1. if let firstRoomName = john.residence?[0].name {
  2. print("The first room name is \(firstRoomName).")
  3. } else {
  4. print("Unable to retrieve the first room name.")
  5. }
  6. // Prints "Unable to retrieve the first room name."

此下标调用中的可选链接问号被放置在下john.residence标括号之前,之后,因为john.residence这是尝试进行可选链接的可选值。

同样,您可以尝试通过带有可选链接的下标设置新值:

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

此下标设置尝试也失败了,因为residence当前为nil

如果您创建一个实际Residence实例并将其分配给john.residence,并Room在其rooms数组中包含一个或多个实例,则可以使用Residence下标rooms通过可选的链接访问数组中的实际项目:

  1. let johnsHouse = Residence()
  2. johnsHouse.rooms.append(Room(name: "Living Room"))
  3. johnsHouse.rooms.append(Room(name: "Kitchen"))
  4. john.residence = johnsHouse
  5. if let firstRoomName = john.residence?[0].name {
  6. print("The first room name is \(firstRoomName).")
  7. } else {
  8. print("Unable to retrieve the first room name.")
  9. }
  10. // Prints "The first room name is Living Room."

访问可选类型的下标

如果下标返回的是可选类型的值(例如,SwiftDictionary类型的键下标),则在下标的右括号后面放置一个问号以链接到其可选的返回值上:

  1. var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
  2. testScores["Dave"]?[0] = 91
  3. testScores["Bev"]?[0] += 1
  4. testScores["Brian"]?[0] = 72
  5. // the "Dave" array is now [91, 82, 84] and the "Bev" array is now [80, 94, 81]

上面的示例定义了一个名为的字典testScores,其中包含两个将键映射StringInt值数组的键值对。该示例使用可选链接将"Dave"数组中的第一项设置为91;。使"Bev"数组中的第一项增加1; 并尝试将数组中的第一项设置为的键"Brian"。前两个调用成功,因为testScores字典包含"Dave"和的键"Bev"。第三次调用失败,因为testScores字典不包含的键"Brian"

链接多个级别的链接

您可以将多个级别的可选链接链接在一起,以深入挖掘模型中更深层的属性,方法和下标。但是,多个级别的可选链接不会为返回值添加更多级别的可选性。

换一种方式:

  • 如果您尝试检索的类型不是可选的,则由于可选的链接,它将变为可选的。
  • 如果你正在尝试检索类型是已经可选的,它不会变得因为链接可选。

所以:

  • 如果尝试Int通过可选的链接检索值,Int?则无论使用多少级链接,总是会返回an 。
  • 同样,如果您尝试Int?通过可选的链接检索值,Int?则无论使用多少级链接,总是会返回an 。

以下示例尝试访问的street属性addressresidence属性john。有2个可选链接的水平在这里使用,以链通过residenceaddress性能,这两者都是可选类型:

  1. if let johnsStreet = john.residence?.address?.street {
  2. print("John's street name is \(johnsStreet).")
  3. } else {
  4. print("Unable to retrieve the address.")
  5. }
  6. // 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实例设置为的值john.residence.address,并为地址的street属性设置了实际值,则可以street通过多级可选链访问该属性的值:

  1. let johnsAddress = Address()
  2. johnsAddress.buildingName = "The Larches"
  3. johnsAddress.street = "Laurel Street"
  4. john.residence?.address = johnsAddress
  5. if let johnsStreet = john.residence?.address?.street {
  6. print("John's street name is \(johnsStreet).")
  7. } else {
  8. print("Unable to retrieve the address.")
  9. }
  10. // Prints "John's street name is Laurel Street."

在此示例中,设置的address属性的尝试john.residence将成功,因为john.residence当前的值包含有效Residence实例。

链接具有可选返回值的方法

前面的示例显示了如何通过可选链接检索可选类型的属性的值。您还可以使用可选链接来调用返回可选类型值的方法,并根据需要链接该方法的返回值。

下面的示例通过可选的链接调用Address类的buildingIdentifier()方法。此方法返回type的值String?。如上所述,在可选链接之后,此方法调用的最终返回类型也是String?

  1. if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
  2. print("John's building identifier is \(buildingIdentifier).")
  3. }
  4. // Prints "John's building identifier is The Larches."

如果你想在这个方法的返回值进行进一步的可选链接,将链接可选问号后,该方法的括号:

  1. if let beginsWithThe =
  2. john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {
  3. if beginsWithThe {
  4. print("John's building identifier begins with \"The\".")
  5. } else {
  6. print("John's building identifier doesn't begin with \"The\".")
  7. }
  8. }
  9. // Prints "John's building identifier begins with "The"."

笔记

在上面的例子中,您将可选链接问号的括号内,因为你要串联上可选的值是buildingIdentifier()方法的返回值,而不是buildingIdentifier()方法本身。