Optional Chaining介绍
关于「optional chaining」,《The Swift Programming Language》是这么描述的:
Optional chaining is a process for querying and calling properties, methods, and subscripts on an optional that might currently be
nil
. If the optional contains a value, the property, method, or subscript call succeeds; if the optional isnil
, the property, method, or subscript call returnsnil
. Multiple queries can be chained together, and the entire chain fails gracefully if any link in the chain isnil
.
P.S:「Optional Chaining」有各种各样的翻译,譬如「自判断链接」「可选链表」等,个人觉得「可选链表」还凑合,本文尽量使用英文表述。
P.S:Swift中的「optional chaining」有些类似于OC中「向nil发送消息」,简而言之,在OC中,向一个对象(指针)发送消息时,若这个指针为nil,则啥都不干;Swift的「optional chaining」有类似的效果;只不过OC这种处理只适用于类对象,Swift的「optional chaining」适用于任意类型。此外,在OC中,我们想一个可能为nil的对象发送一个消息时,通常我们不知道这个消息是否被执行了(尤其是该消息么有返回值时),但在Swift中,可以通过检查返回值来判断实例的方法是否被调用,后文会详细阐述。
可选链表 v.s 强制解包
在Swift中,访问某些属性/方法/下标得到的经果常常是一个optional,在这些返回值为optional的属性/方法/下标后放上一个问号?
就构成了所谓的「optional chaining」。
这有一些类似于强制解包(在可选类型后面放上一个感叹号!
强制解包)。他们的在于当optional为nil时「optional chaining」即刻失败,然而一般的强制解包操作会引发runtime错误。
下面这段话非常重要。
因为「optional chaining」是随时否可以提前返回nil的,所以使用optional chaining所得到的东西都是optional,even if the property, method, or subscript you are querying returns a non-optional value. 下面以代码来演示:
class Toy {
let name: String
init(name: String) {
self.name = name
}
} class Pet {
var toy: Toy?
} class Child {
var pet: Pet?
}
在实际使用过程中,我们想要知道小明(名为xiaoming
的Child对象)的宠物的玩具的名字的时候,可以通过下面的「optional chaining」查询:
let toyName = xiaoming.pet?.toy?.name
注意,我们最后访问的是name,并且在Toy的定义中name是被定义为一个确定的String
而非String?
的,但是我们拿到的toyName起始还是一个String?
的类型。这是由于在「optional chaining」中我们在任意一个?.
的时候都可能遇到nil而提前返回,这个时候当然只能获取到nil了。
所以,在实际使用中,我们大多数情况下可能更希望使用「Optional Binding」来直接取值,如下:
if let toyName = xiaoming.pet?.toy?.name {
// 巴拉巴拉
}
总之,使用「optional chaining」的返回结果一定是一个optional。
OK,现在以几段代码来解释「可选链表」(即?.
)和「强制解包」(即!.
)的不同。
首先定义两个类Person和Residence,如下:
class Person {
var residence: Residence?
} class Residence {
var numberOfRooms =
}
Residence具有一个Int
类型属性numberOfRooms,其值为1。Person具有一个optional属性residence,它的类型是Residence?
。
如果你创建一个新的Person实例,它的residence属性由于是被定义为自判断型的,此属性将默认初始化为空:
let john = Person()
如果你想使用声明符!
强制解包获得这个人residence属性的numberOfRooms属性值,将会引发运行时错误,因为这时没有可以供拆包的residence值,如下:
let roomCount = john.residence!.numberOfRooms
// 将导致运行时错误
当john.residence不是nil时,会正常运行,且会将roomCount设置为一个Int
类型的合理值。然而,如上所述,当residence为空时,这个代码将会导致运行时错误。
自判断链接提供了一种另一种获得numberOfRooms的方法。利用自判断链接,使用问号来代替原来!
的位置:
if let roomCount = john.residence?.numberOfRooms {
println("John's residence has \(roomCount) room(s).")
} else {
println("Unable to retrieve the number of rooms.")
} /* 输出:
Unable to retrieve the number of rooms.
*/
通过Optional Chaining访问属性
正如上文可选链表 v.s 强制解包所述,你可以利用「optional chaining」获取属性,并且检查属性是否成功。
使用上述定义的类来创建一个人实例,并再次尝试后去它的numberOfRooms属性:
let john = Person()
if let roomCount = john.residence?.numberOfRooms {
println("John's residence has \(roomCount) room(s).")
} else {
println("Unable to retrieve the number of rooms.")
}
/*输出:
Unable to retrieve the number of rooms.
*/
由于john.residence是空,所以这个自判断链接和之前一样失败了,但是没有运行时错误。
你还可以使用「optional chaining」来设置属性值,如下:
john.residence?.numberOfRooms =
通过Optional Chaining调用方法
你可以使用「optional chaining」的调用某个optional的方法,并可以检查方法调用是否成功,哪怕这个方法没有返回值。
在上文的Residence中添加一个实例方法printNumberOfRooms
,该方法会打印numberOfRooms的当前值。方法如下:
func printNumberOfRooms(){
println("The number of rooms is \(numberOfRooms)")
}
这个方法没有返回值。但是,在Swift中,若函数和方法没有显式提供返回值,则Swift会为它们提供一个隐式的返回值类型Void
。如果你利用自判断链接调用此方法,这个方法的返回值类型将是Void?
,而不是Void
,因为当通过「optional chaining」调用方法时返回值总是optional,即使是这个方法本是没有定义返回值,你也可以使用if
语句来检查是否能成功调用printNumberOfRooms方法:如果方法通过自判断链接调用成功,printNumberOfRooms的隐式返回值将会是Void
,如果没有成功,将返回nil:
if john.residence?.printNumberOfRooms() != nil {
println("It was possible to print the number of rooms.")
} else {
println("It was not possible to print the number of rooms.")
}
/*输出:
It was not possible to print the number of rooms.
*/
通过Optional Chaining访问下标
还可以在「optional chaining」中通过「下标」(subscripts)获取和设置一个optional,同样可以检查是否获取成功;
通过「Optional Chaining」访问下标时,一定要注意:
When you access a subscript on an optional value through optional chaining, you place the question mark (
?
) before the subscript’s braces ([]
), not after. The optional chaining question mark always follows immediately after the part of the expression that is optional.
举个栗子,在上文Person和Residence的基础上添加一个新的类Room:
class Room {
let name: String
init(name: String) {
self.name = name
}
}
在Residence类中添加一个subscript和一个数组属性rooms,如下:
class Residence {
...
var rooms = [Room]()
subscript(i: Int) -> Room {
return rooms[i]
}
...
}
通过「Optional Chaining」访问下标如下:
if let firstRoomName = john.residence?[].name {
println("The first room name is \(firstRoomName).")
} else {
println("Unable to retrieve the first room name.")
} /*输出:
Unable to retrieve the first room name.
*/
同样,除了获取,还可以设置:
john.residence?[] = Room(name: "Bashroom")