第十六章 可选链
可选链是查询和调用在属性,方法或下标的一个当前可能为nil
的一个可选类型。如果说这个可选类型现在含有一个值,那么就回成功调用该类型中的属性,方法或下标。如果这个可选类型是nil那么在调用属性,方法或下标的时候回返回nil,多个查询指定可以链接在一起,如果说该链接中的任何一个链接返回nil,那么整个可选链就会失败。
1. Optional Chaining as an Alternative to Forced Unwrapping (可选链可作为强制展开使用)
我们可以添加(?
)在某个类型的属性,方法或下标的可选值(non-nil)的后面,用来指出声明该可选链。这种的方法就和强制展开那样在某个可选类型的后面添加(!
)是一样的。可选链和强制展开最大的不同就在与可选链的失败在以这个可选的值是nil,而强制展开当这个可选是nil的时候则会触发一个runtime error。
可选链的调用总是在这个可选类型的值,反应出了这个可选链总是会被调用在一个nil值上面。 即便是这个属性,方法或者是下标我们在查询的时候会返回一个非可选的值。 我们可以使用这个返回的可选值来检查是否这个可选链的调用是否成功(返回的可选类型含有一个值),或者是基于链中的某个值是nil的情况,这个可选链的调用就回失败。
相同类型中,可选链的调用结果用是有一个期望的返回值,通常返回Int
的属性返回的是Int?
,当我们用此可选链的方法来读取这个类型的属性的时候。下面这个几个例子,就是成功调用可选链和和强制展开之间不同的地方。
class Person {
// 可选类型的属性 有返回值且都是nil
var residence: Residence?
}
// 两者不同的是值类型不一样,导致的结果不一样,相同的是都有值。
class Residence {
// 类的属性有具体的值
var numberOfRooms = 1
}
// 实例化后的引用 Person类是仅有一个值为nil的属性
let john = Person()
// 这样在调用属性的时候 展开Person属性值,返回的只是nil
let roomCount = john.residence!.numberOfRooms
// 使用可选绑定 来强制展开 所以是一个可选链和强制展开的一个综合的例子
if let roomCount = john.residence?.numberOfRooms {
print("John's residence has \(roomCount) room(s).")
} else {
// 返回值为nil 所以调用的可选链就失败了
print("Unable to retrieve the number of rooms.")
}
// 输出:Unable to retrieve the number of rooms.
总结:这个类中的最关键的就是!
和?
的使用,其中这个!
表示的是把某个属性的值强制展开和读取它,如果有值那么则引用这个值,没有值则是nil说明这个可选类型被引用的时候会导致可选链失败,而?
在上面的例子中表示”链接“这个可选residence属性和用来检索numberOfRooms中的值,如果这个residence存在。
2. Defining Model Classes for Optional Chaining (为可选链定义的模型类)
我们可以更进一步对可选链于属性,方法和下标来调用,这样调用会是我们更加细分对一个相同类型中复杂模型的划分为一个或多个子属性。从而用来检查是否可以读取这些属性,方法,下标和子属性。
下面这段代码定义了四种模型类(model class
)可用于随后这几个案例里。包括了多层级(multilevel)可选链,结合上面的那个Person
和Residence
模型,并添加Room和Address类和相对应结合的属性,方法和下标。
// 此类定义和上面是一样的
class Person {
var residence: Residence?
}
但是下面的这个Residence类要比原来的定义更加复杂。定义了一个变量属性Rooms
空白的数组初始化的[Room]:
class Residence {
// 实例属性(instance property)
var rooms = [Room]()
// 计算属性
var numberOfRooms: Int {
return rooms.count
}
// read and write下标 参与room值获取和设定
subscript(i: Int) -> Room {
get {
return rooms[i]
}
set {
rooms[i] = newValue
}
}
func printNumberOfRooms() {
print("The number of rooms is \(numberOfRooms)")
}
// 可选类型的Address
var address: Address?
}
// 类Room用于rooms数组,
class Room {
// room的一个属性叫做name
let name: String
// 构造器用来构造房间的名字
init(name: String) { self.name = name }
}
最后这个类在模型中被称为Address,总体上这三个模型的类的划分和之间的关系也是很明确的。该类有三个可选的属性类型String?
。 前两个可选属性buildingName
和buildingNumber
作为二选一的方法来鉴别这个建筑物的地址。第三个属性street用于命名那个建筑物所在的地址street名。
这个Address类还有一个方法buildingIdentifier()
它有一个返回类型String?
,这个方法用于检查地址的属性和返回的建筑物名称是否存在某个值,或者这个buildingNumber和street是否都有值,或者没值,或者值是nil。
该类中的方法buildingIdentifier()
其实是挺有意思的一部分,它用了一个可选绑定来强制读取(即便读取到的是nil或者空值)buildNumber和Street的值,buildingName的值,在或者什么值都没有返回nil。用if-else条件语句和if let可选绑定强制读取这些值在这个Address类里面。
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
}
}
}
3. Accessing Property Through Optional Chaining (通过可选链读取属性)
在可选链作为强制展开使用片段里面,我们可以使用可选链来读取某个可选值,从而来检查某个属性值的读取是否成功。
结合上面那些以定义的类 创建一个新的Person实例,尝试去读取numberOfRooms属性
let john = Person()
// 因为john.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.")
}
// 输出:Unable to retrieve the number of rooms.
尝试通过可选链给属性设置一个值,这样就可以读取属性了
let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
// 可选链会失败,因为这是的residence依然是nil
john.residence?.address = someAddress
下面的这个也是可选链的一部分,用函数创建一个Address,然后在返回这个address,在重新分配值外加引用createAddress(),但是这个函数并不会被调用,什么都不会输出。
func createAddress() -> Address {
print("Function was called.")
let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
return someAddress
}
// residence里面现在还是nil. 调用了函数也不会有输出
john.residence?.address = createAddress()
4. Calling Methods Through Optional Chaining (通过可选链来调用方法)
我们可以用可选链的可选值来调用方法,来检查这个方法是否成功调用,即使这个方法并没有定义一个返回值。下面residence类的这个方法printNumberOfRooms()
输出的是当前numberOfRooms
的值。
func printNumberOfRooms() {
print("The number of rooms is \(numberOfRooms)")
}
上面的这个方法并没有指定返回的类型,然而没有返回类型的方法和函数回隐含地返回类型Void,关于函数和方法没有返回值的情况在相关章节中有详细介绍。也就是说没有详细说明返回类型的函数或方法它们的返回值是()
里面的值或者是空白的元组值。
如果说我们可以以使用可选链中可选值的方式来调用这个方法,而这个方法的返回类型将会是Void?
而不是Void
,因为当我们通过可选链来调用方法的时候,返回的值永远都是可选类型。这样就允许我们使用if-else条件语句来检查是否可能调用printNumberOfRooms()
方法。即便是这个方法并没有定义返回值,来比较如果说这个方法返回的不是是nil (if语句),那么调用方法成功,反之则失败(else语句),
/* 当前residence属性是nil,所以在nil里面读取rintNumberOfRooms()那还是nil
所以说if语句的 != 左边值和右边值都是nil 应当用 = , 所以则输出else语句。*/
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.
道理是相同的,就算我们通过可选链给residence的属性设置一个值,上面的例子通过可选链读取属性,尝试给john.residence
设置一个地址值,因为residence
属性是nil,任何尝试通过可选链给这个属性设置值的行为 都会返回Void?
(空白的可选类型值)。返回类型Void?允许我们来检查设置的属性值是否成功。
/* john.residence还是nil 在nil中读取address 还是nil
而这个赋值someAddress是类Address里面的属性 与类Person没有半毛钱关系 */
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.
5. Accessing Subscripts Through Optional Chaining (通过可选链来读取下标)
可以使用可选链在下标的可选值上面来读取和设置值,并且可以用此来检查这个调用下标是否成功。当我们通过可选链来读取下标中的可选值是,在下标的括号前面添加?
,在其之后紧跟着可选链的可选部分。
下面这个例子尝试索取rooms数组中第一个room的名字,该数组定义在Residence类的john.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.")
}
// 输出:Unable to retrieve the first room name.
john.residence?[0] = Room(name: "Bathroom")
创建一个实例johnsHouse并且引用到Residence类 在用下标语法进行赋值操作,
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.
5.1 Accessing Subscripts of Optional Type (读取可选类型中的下标)
如果说一个下标返回的是一个可选类型的值。如字典类型中使用下标,在下标的后括号()
)后面添加问号(?
)来’链接‘可选类型中的可选值。
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类型的key
和Int
类型的值。这个例子使用可选链来设置第一个物品在Dave
数组中一值到91。以每次增加1的方式来增加物品在这个Bev
数组里,并且尝试在数组中设置第一个key-Brain。数组中的前两个物品调用是成功的,而第三个调用则回失败,因为testScores
字典并没有为Brain设置一个key,而这个72值是为第三个物品设置的(value)值。
6. Linking Multiple Levels of Chaining (连接链的多层级)
可以把多层级的可选链连接在一起,这样才能更加深入到模型中的属性,方法和下标。但是,多层级的可选链不会为返回的值天价更多层级的可选性。
换句话说:
- 如果我们尝试检索的类型不是可选类型,该类型将会变成可选的类型因为有可选链
- 如果我们尝试检索的类型已经是可选类型,该类型将不会变成可选的类型应为有"链"
因此:
- 如果我们尝试通过可选链来检索某个
Int
的值,返回的类型永远是Int?
不管我们使用了多少层的链。 - 同样的道理,如果我们尝试通过可选链来检索Int?中的值,返回的类型永远是Int?。 不管我们使用了多少层的链。
下面这个例子呢 就是尝试检索john中的residence中的address中的street属性。在这里有两个层级的可选链。通过链接residence和address属性来获取street的属性值。链接的residence和address都是可选类型。
// 前面实例化过的residence 所以residence现在不是nil
if let johnsStreet = john.residence?.address?.street {
print("John's street name is \(johnsStreet).")
} else {
// 而为john设置的address是nil 所以调用多层可选链失败 输出else语句
print("Unable to retrieve the address.")
}
// 输出:Unable to retrieve the address.
上面这个例子需要注意的是 检索street属性的值是一个可选类型的String?,因此john.residence?.address?.street
的返回值也是String?。不管这个可选链有多少个层级,最后返回的值就是String?
创建一个Address的实例作为john.residence.address。并且为street的属性设置一个值,这样我们就可以通过可选链来读取street的属性值了。
let johnsAddress = Address()
johnsAddress.buildingName = "The Larches"
johnsAddress.street = "Laurel Street"
john.residence?.address = johnsAddress
// 现在的address属性不是nil了 所以调用可选链读取属性值就回成功
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.
7. 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).")
}
// 输出: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\".")
}
}
// 输出:John's building identifier begins with "The".