本文系阅读阅读原章节后总结概括得出。由于需要我进行一定的概括提炼,如有不当之处欢迎读者斧正。如果你对内容有任何疑问,欢迎共同交流讨论。
关于什么时候应该用强制类型解封,网上有各种的答案,有人说永远别用,有人说如果能让代码更简单就用,也有人说如果你不能避免使用!
时可以用。一个比较推荐的原则是:
当你十分确定可选类型不会为nil,而且你希望一旦它为nil,你的程序就崩溃时,使用
!
强制解封可选类型。
以上面的flateen
方法为例:
func flatten<S: SequenceType, T where S.Generator.Element == T?>(source: S) -> [T] {
return source.lazy.filter { $0 != nil}.map { $0! }.array
}
在这里,map
方法中的$0
永远不会是nil
,因为在前一步的操作中nil
已经被过滤掉了。此时我们完全有理由使用!
强制解封可选类型。
但这样的例子毕竟少见,利用之前所讲的各种技术,很有可能会有比直接使用强制解封更好的解决问题的办法。在真的打算使用!
之前,还是应该回想一下是否确实没有别的方法了。
不过有时候我们非常确定某个可选类型不是nil
,如果不幸是nil
,宁可程序直接崩溃也不继续运行,以免导致一个很严重的逻辑错误。在这种情况下,!
起到了一个"解封或报错"运算符的作用,这比使用可选链或用if
判断是否为nil
更好,因为这样不会隐藏某些理论上不可能的情况。一旦实际情况中遇到了nil
,程序崩溃,开发者就会立刻得知。
##改进强制解封的报错信息
我们可以提供一个!!
运算符,它在解封可选类型的同时提供了更加详细的报错信息,在程序崩溃时可以把报错信息打印出来:
infix operator !! {}
func !! <T>(wrapped: T?, @autoclosure failureText: () -> String) -> T {
if let x = wrapped { return x }
fatalError(failureText())
}
现在我们就可以写一些更加详细的报错信息了,其中可以包括想要解封的值的信息:
let s = "foo"
let i = Int(s) !! "Expecting integer, got \"\(s)\""
@autoclosure
确保我们只在需要的时候才使用第二个操作数。
Debug和Release中的断言
通常情况下,我们倾向于在debug版本使用断言然后进行一些测试。然后在实际的产品中,用一个默认值替代nil
,比如0或者空数组等。
我们定义一个问叹号运算符?!
,如果解封失败则触发断言,在release版本断言不会触发,所以我们就提供一个默认值:
infix operator !? {}
func !?<T: IntegerLiteralConvertible>(wrapped: T?, @autoclosure failureText: () -> String) -> T {
assert(wrapped != nil, failureText())
return wrapped ?? 0
}
于是下面这段代码在debug模式下触发断言,在release模式下则会返回0:
let i = Int(s) !? "Expecting integer, got \"\(s)\""
我们可以定义一个范型的问叹号!?
,它可以由调用者手动提供默认值,这样就不用依赖于某个具体的类型。它的参数是一个元组,里面有默认值和错误信息:
func !?<T>(wrapped: T?, @autoclosure nilDefault: () -> (value: T, text: String)) -> T {
assert(wrapped != nil, failureText())
return wrapped ?? nilDefault.value
}
//debug 时触发断言, release 时返回5
Int(s) !? (5, "Expected integer")
由于在可选链中,原来返回值为Void
的方法,最终返回了Void?
,我们也写一个非泛型的版本来检测可选链在哪里遇到了nil
:
func !?(wrapped: ()?, @autoclosure failureText: () -> String) {
assert(wrapped != nil, failureText)
}
var output: String? = nil
output?.write("something") !? "Wasn't expecting chained nil here"
不管是问叹号还是双感叹号,本质上都是定义了一个双目运算符。左侧接收可选类型并且尝试解封,右侧提供相应的调试信息。