可选链是在一个现在可能是nil的可选值查询和调用属性,方法,和下标的过程。如果可选类型包含一个值,属性,方法,或者下标调用成功;如果可选是nil,属性,方法,或者下标调用返回nil。多个查询可以同时链起来,如果链条中任何链接是nil整个链条优雅的失败。
swift中的可选链条像Objective-C中的nil信息传递,但是用一种任何类型都可用的方式,可以检查成功或者失败。
可选链作为另一种方式强解包(Optional Chaining as an Alternative to Forced Unwrapping)
如果可选类型使非nil的你希望调用属性,方法或者下标的可选值后面防止一个问号来说明可选链。这和在可选值后面放置一个感叹号来强行解包它的值很相似。不同的是,当可选值是nil的时候可选链条优雅的失败,当可选类型使nil时强行解包触发一个运行时错误。
要反应可选链条可以调用在一个nil值上的情况,可选链条的调用结果通常是一个可选值,即使你查询的属性,方法,或者下标返回的是一个非可选类型的值。你可以用这个可选的返回值来检查可选链条调用是否成功(返回的可选类型包含一个值),或者由于链中的一个nil值没有成功(返回的可选值时nil)。
特别的,调用一个可选链的结果和期望的返回值一样的类型,但是包装在一个可选类型中。当通过可选链来访问的时候一个通常返回Int的属性会返回一个Int?。
下面多个代码块解释了可选链条和强解包有什么不同并且使你可以检查是否成功。
首先定义了两个名为Person和Residence的类:
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}Residece实例有一个单独的Int属性名为numberOfRooms,有一个默认值1.Person实例有一个Residence?类型的可选属性residence。
如果你创建了一个新的Person实例,它的residence属性默认初始化为nil,因为是可选的。下面的代码,john有一个值为nil的residence属性:
let john = Person()如果你尝试访问这个人的residence的numberOfRooms属性,通过在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属性并且如果residence存在的时候获取numberOfRooms的值。
因为尝试访问numberOfRooms潜在可能失败,可选链尝试返回一个Int?类型的值,或者“optional 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,现在会返回一个包含默认值位1的numberOfRooms的Int?:
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)."为可选链定义模型类(Defining Model Classes for Optional Chaining)
我们可以用调用超过一层深度的属性,方法和下标来使用可选链。这使你可以在复杂的相互关联的类型模型之中向下获取到子属性,检查是否可能在这些子属性中访问属性,方法,和下标。
下面的代码段定义了四个为在多个子序列例子中应用的模型类,包括多层可选链的例子。这些类通过增加一个Room和Address类来向上扩展上面的Person和Residence模型,用关联的属性,方法,和下标。
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属性作为一个计算属性实现,不是一个存储属性。计算属性numberOfRooms属性简单的返回了rooms数组的count属性。
作为它的rooms数组的快捷访问,这个版本的Residence提供一个读写下标来提供在Rooms数组中的请求索引位置的访问。
这个版本的Residence也提供了一个名为printNumberOfRooms的方法,简单打印在residence中的rooms的个数。
最后,Residence定义了一个名为address的可选属性,类型是Address?。
这个属性的Address类类型在下面定义。
用于rooms数组的Room类是一个用一个名为name的属性的简单类,和一个把属性设置为合适的room名字的初始化器。
class Room {
let name: String
init(name: String) { self.name = name }
}宰割模型的最后一个类名为Address。这个类有三个String?类型的可选属性。前两个属性,buildingName和buildingNumber,是在一个address中确定一个特定building的两种方式。第三个属性,street,用来命名address的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?的返回类型。这个方法检查address的属性并且如果他有值的话返回buildingName,或者如果两个都有值的话串联了street的buildingName,否则是nil。
通过可选链访问属性(Accessing Properties Through Optional Chaining)
像在Optional Chaining as an Alternative to Forced Unwrapping,中说明的,你可以使用可选链访问在可选值中访问一个属性,如果属性访问成功了要检查。
使用上面的类定义来创建一个新的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没有执行,因为访问一个常量没有其他表现。下面的列表做了一样的分配,但是他用一个函数来创建address。再返回值之前函数打印“Function was called”,让你看到=右边是否执行。
func createAddress() -> Address {
print("Function was called.")
let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
return someAddress
}
john.residence?.address = createAddress()可以看到createAddress()函数没有调用,因为没有东西打印。
通过可选链调用方法(Calling Methods Through Optional Chaining)
你可以使用可选链来在可选值上调用一个方法,并且检查那个方法是是否调用成功。即使方法没有定义返回值你也可以这样做。
Residence类的printNumberOfRooms()方法打印numberOfRooms当前的值,这里是方法的样子:
func printNumberOfRooms() {
print("The number of rooms is \(numberOfRooms)")
}这个方法没有置顶返回类型。不过,没有返回值的函数和方法有一个潜在的Void返回类型,像在Functions Without Return Values中描述的。这意味着他们返回了一个()值,或者一个空元祖。
如果你用可选链在可选值上调用这个方法,方法的返回值将是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."如果你尝试通过可选链设置一个属性也是可以的。上面在Accessing Properties Through Optional Chaining中的例子尝试给john.residence设置一个address值,即使residence属性是nil。任何通过可选链设置一个属性的尝试返回一个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."通过可选链访问下标(Accessing Subscripts Through Optional Chaining)
你可以使用可选链来尝试从一个可选值的下标中获取和设置一个值,并且检查下表调用是否成功了。
当你通过可选链在一个可选值上访问一个下标的时候,在下标的方括号前面而不是后面放置一个问号。可选链的问号通常跟在部分可选表达式的后面
下面的例子尝试使用定义在Residence类中的下标来获取john.residence属性的rooms数组的第一个room的名字。因为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。
如果你给john.residence创建和分配一个实际的Residence实例,在它的rooms数组中用一个或者多个Room实例,你可以通过可选链用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."访问可选类型的下标(Accessing Subscripts of Optional Type)
如果一个下标返回一个可选类型的值--例如swift的字典类型的key下标--在下标的闭方括号后面放一个问号来链接它的可选返回值:
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的字典,包含两个key-value对用一个String的key映射Int值的数组。例子中使用可选链将“Dave”数组的第一个对象设置为91;“Bev”中的第一个对象加1;尝试设置”Brian“的key对应的数组的第一个对象。前两个调用成功了,因为testScores字典包含“Dave”和“Bev”的键。第三个调用失败了,因为testScores字典没有包含对应“Brian”的键。
链接多层的链(Linking Multiple Levels of Chaining)
可以把可选链的多个层链接在一起来在一个模型中向下获取属性,方法,和下标。不过,可选链的多个层级不给返回值增加更多的可选层级。
换句话说:
- 如果你尝试获取的类型不是可选的,由于可选链它会变成可选的。
- 如果你尝试获取的类型是可选类型,他不会因为链条变成更多可选。
所以:
- 如果你尝试通过可选链获取一个Int值,通常会返回Int?,不管用了多少层链。
- 同样的,如果你尝试通过可选链获取一个Int?值,通常返回Int?,不过用了多少层链。
- 下面的例子尝试访问john的residence属性的address属性的street属性。这里用了两层可选链,链接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?。所以johm.residence?.address?.street的返回值是String?,即使除了下层的属性可选类型之外用了两层可选链。
如果你把真实的Address实例设置为john.residence.address的值,给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."在这个例子中,尝试设置john.residence的address属性成功,因为john.residence的值当前包含一个有效的Residence实例。
用可选返回值的方法链(Chaining On Methods with Optional Return Values)
上面的例子展示了如何通过可选链路获取可选类型属性的值。你也可以使用可选链路调用一个返回可选类型值的方法,如果需要可以链到方法的返回值。
下面的例子通过可选链路调用Address类的buildingIdentifier()方法。这个方法返回一个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()方法自己。
如果数组是可选的,数组名后面跟?。如果数组下标返回的是可选类型,方括号后跟?。
如果字典是可选的,字典名后面跟?。字典下标返回的是可选类型,方括号后跟?。
可选类型是一个枚举