第四章——何时使用强制解封

146 阅读3分钟

本文系阅读阅读原章节后总结概括得出。由于需要我进行一定的概括提炼,如有不当之处欢迎读者斧正。如果你对内容有任何疑问,欢迎共同交流讨论。

关于什么时候应该用强制类型解封,网上有各种的答案,有人说永远别用,有人说如果能让代码更简单就用,也有人说如果你不能避免使用!时可以用。一个比较推荐的原则是:

当你十分确定可选类型不会为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"

不管是问叹号还是双感叹号,本质上都是定义了一个双目运算符。左侧接收可选类型并且尝试解封,右侧提供相应的调试信息。