Apple为了推广和介绍Swift,破天荒地为这门语言开设了一个博客 (当然我觉着是因为Swift“坑”太多需要一个地方来集中解释)。其中有一篇 提到了一个叫作@autoclosure的关键词。
@autoclosure可以说是Apple的一个非常神奇的创造,因为这更像是在“hack”这门语言。简单说,@autoclosure做的事情就是把一句表达式自动地 封装 成一个闭包(closure)。这样有时候在语法上看起来就会非常漂亮。
比如我们有一个方法接受一个闭包,当闭包执行的结果为true的时候进行打印:
在调用的时候,我们需要写这样的代码:
当然,在Swift中对闭包的用法可以进行一些简化,在这种情况下我们可以省略掉return,写成:
还可以更进一步,因为这个闭包是最后一个参数,所以可以用尾随闭包(trailing closure)的方式把大括号拿出来,然后省略括号,写成:
但是不管哪种方式,要么书写起来十分麻烦,要么表达上不太清晰,看起来都让人很不舒服。于是@autoclosure登场了。我们可以改换方法参数,在参数名前面加上@autoclosure关键字:
这时候我们就可以直接使用下面的代码进行调用:
Swift会把2 > 1这个表达式自动转换为()-> Bool。这样我们就得到了一个写法简单,表意清楚的式子。
在Swift中,有一个非常有用的操作符,可以用来快速地对nil进行条件判断,那就是??。这个操作符可以判断输入内容,并在当左侧的值是非nil的Optional值时返回其value,当左侧是nil时返回右侧的值,比如:
在这个例子中我们没有设置过level,因此最后startLevel被赋值给了currentLevel。如果我们充满好奇心地查看??的定义,可以看到??有两种版本:
在这里我们的输入满足的是后者,虽然表面上看startLevel只是一个Int,但其实在使用时它被自动封装成了一个()-> Int,有了这个提示,我们不妨来猜测一下??的实现吧:
可能你会有疑问,为什么这里要使用autoclosure,直接接受T作为参数并返回不行吗?这正是autoclosure的一个最值得称赞的地方。如果我们直接使用T,那么就意味着在??操作符真正取值之前,我们就必须准备好一个默认值,这个默认值的准备和计算是会降低性能的。但如果optional不是nil,就完全不需要这个默认值,而会直接返回optional解包后的值。这样一来,默认值就白白准备了,这样的开销是完全可以避免的,方法就是将默认值的计算推迟到optional判定为nil之后。
就这样,我们可以巧妙地绕过条件判断和强制转换,以很优雅的写法处理对optional及默认值的取值了。最后要提一句的是,@autoclosure并不支持带有输入参数的写法,也就是说只有形如()-> T的参数才能使用这个特性进行简化。另外因为调用者往往容易忽视@autoclosure这个特性,所以在写接受@autoclosure的方法时还请特别小心,在容易产生歧义或者误解的时候,还是使用完整的闭包写法比较好。
在Swift1.2中,@autoclosure的位置发生了变化。现在@autoclosure需要像本文中这样,写在参数名的前面作为参数修饰,而不是在类型前面作为类型修饰。但是现在标准库中的方法签名还是写在了接受的类型前面,这应该是标准库中的疏漏。在我们自己实现autoclosure时,在类型前修饰的写法在Swift1.2中已经无法编译了。
练习
在Swift中,其实&&和||这两个操作符里也用到了@autoclosure。作为练习,不妨打开Playground,试试看怎么实现这两个操作符吧?