使用 Optional Chaining 可以让我们摆脱很多不必要的判断和取值,但是在使用的时候需要小心陷阱。
因为 Optional Chaining 是随时都可能提前返回 nil 的,所以使用 Optional Chaining 所得到的东西其实都是 Optional 的。比如有下面的一段代码:
class Toy {
let name: String
init(name: String) {
self.name = name
}
}
class Pet {
var toy: Toy?
}
class Child {
var pet: Pet?
}
在实际使用中,我们想要知道小明的宠物的玩具的名字的时候,可以通过下面的 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 {
// 太好了,小明既有宠物,而且宠物还正好有个玩具
}
可能单独拿出来看会很清楚,但是只要稍微和其他特性结合一下,事情就会变得复杂起来。来看看下面的例子:
extension Toy {
func play() {
//...
}
}
我们为 Toy 定义了一个扩展,以及一个玩玩具的 play() 方法。还是拿小明举例子,要是有玩具的话,就玩之:
xiaoming.pet?.toy?.play()
除了小明也许我们还有小红小李小张等等..在这种时候我们会想要把这一串调用抽象出来,做一个闭包方便使用。传入一个 Child 对象,如果小朋友有宠物并且宠物有玩具的话,就去玩。于是很可能你会写出这样的代码:
这是错误代码
let playClosure = {(child: Child) -> () in child.pet?.toy?.play()}
这样的代码是没有意义的!
问题在于对于 play() 的调用上。定义的时候我们没有写 play() 的返回,这表示这个方法返回 Void (或者写作一对小括号 (),它们是等价的)。但是正如上所说,经过 Optional Chaining 以后我们得到的是一个 Optional 的结果。也就是说,我们最后得到的应该是这样一个 closure:
let playClosure = {(child: Child) -> ()? in child.pet?.toy?.play()}
这样调用的返回将是一个 ()? (或者写成 Void? 会更清楚一些),虽然看起来挺奇怪的,但这就是事实。使用的时候我们可以通过 Optional Binding 来判定方法是否调用成功:
if let result: () = playClosure(xiaoming) {
print("好开心~")
} else {
print("没有玩具可以玩 :(")
}