Optional 可以说是 Swift 的一大特色,它完全解决了 “有” 和 “无” 这两个困扰了 Objective-C 许久的哲学概念,也使得代码安全性得到了很大的增加。但是一个陷阱 -- 或者说一个很容易让人迷惑的概念 -- 也随之而来,那就是多重的 Optional。
在深入讨论之前,可以让我们先看看 Optional 是什么。很多读者应该已经知道,我们使用的类型后加上 ? 的语法只不过是 Optional 类型的语法糖,而实际上这个类型是一个 enum:
enum Optional<T> : _Reflectable, NilLiteralConvertible { case None case Some(T) //... }
在这个定义中,对 T 没有任何限制,也就是说,我们是可以在 Optional 中装入任意东西的,甚至也包括 Optional 对象自身。打个形象的比方,如果我们把 Optional 比作一个盒子,实际具体的 String 或者 Int 这样的值比作糖果的话,当我们打开一个盒子 (unwrap) 时,可能的结果会有三个 -- 空气,糖果,或者另一个盒子。
空气和糖果都很好理解,也十分直接。但是对于盒子中的盒子,有时候使用时就相当容易出错。特别是在和各种字面量转换混用的时候需要特别注意。
对于下面这种形式的写法:
var string: String? = "string" var anotherString: String?? = string
我们可以很明白地知道 anotherString 是 Optinal<Optional<String>>。但是除开将一个 Optional 值赋给多重 Optional 以外,我们也可以将直接的字面量值赋给它:
var literalOptional: String?? = "string"
这种情况还好,根据类型推断我们只能将 Optional<String> 放入到 literalOptional 中,所以可以猜测它与上面提到的 anotherString 是等效的。但是如果我们是将 nil 赋值给它的话,情况就有所不同了。考虑下面的代码:
var aNil: String? = nil var anotherNil: String?? = aNil var literalNil: String?? = nil
anotherNil 和 literalNil 是不是等效的呢?答案是否定的。anotherNil 是盒子中包了一个盒子,打开内层盒子的时候我们会发现空气;但是 literalNil 是盒子中直接是空气。使用中一个最显著的区别在于:
if let a = anotherNil { print("anotherNil") } if let b = literalNil { print("literalNil") }
这样的代码只能输出 anotherNil。
另一个值得注意的地方时在Playground 中运行时,或者在用 lldb 进行调试时,直接使用 po 指令打印 Optional 值的话,为了看起来方便,lldb 会将要打印的 Optional 进行展开。如果我们直接打印上面的 anotherNil 和 literalNil,得到的结果都是 nil:
(lldb) po anotherNil nil (lldb) po literalNil nil
如果我们遇到了多重 Optional 的麻烦的时候,这显然对我们是没有太大帮助的。我们可以使用 fr v -R 命令来打印出变量的未加工过时的信息,就像这样:
(lldb) fr v -R anotherNil (Swift.Optional<Swift.Optional<Swift.String>>) anotherNil = Some { ... 中略 } (lldb) fr v -R literalNil (Swift.Optional<Swift.Optional<Swift.String>>) literalNil = None { ... 中略 }
这样我们就能清晰地分辨出两者的区别了。