转载

每周 Swift 社区问答:@autoclosure

Swift 中有很多有用的属性关键字,在这里可以看到一些 介绍 , 不过这篇官方文档也只是大概的把常用的属性关键字做了一个简要的说明。有一些属性关键字,其实理解起来并不是那么容易。

这一期问答来谈谈 @autoclosure 属性。

@autoclosure

用法

此属性关键字用在函数或者方法的闭包参数前面,但是闭包类型被限定在无参闭包上: () -> T 。例如以下的一个例子:

func doSomeOperation(@autoclosure op: () -> Bool) {
op()
}

// 调用如下:

doSomeOperation(2 > 3)

作用

看了以上代码,大家估计会比较困惑,这样定义了去掉 @autoclosure 有什么区别?我们先直接抛出 @autoclosure 的全部作用,然后再来分析,有和没有的区别:

  • 使用 @autoclosure 关键字能简化闭包调用形式
  • 使用 @autoclosure 关键字能延迟闭包的执行

关于第一点,大家可能看上面的例子代码,调用带 @autoclosure 的函数形式很自然,其实如果去掉 @autoclosure ,我们就不能这样调用了:

func doSomeOperationWithoutAutoclosure(op: () -> Bool) {
op()
}

doSomeOperationWithoutAutoclosure({2 > 3})
doSomeOperationWithoutAutoclosure{2 > 3} //尾闭包的简化

很容易看的出来,使用括号来写起来更加自然。其实如果是尾闭包的形式,也可以接受。只是尾闭包只能是放到参数列表的最后才能这样使用。而 @autoclosure 是可以修饰任何位置的参数:

func doSomeOperationWithTwoAutoclosure(@autoclosure op1: () -> Bool, @autoclosure op2: () -> Bool) {
op1()
op2()
}

doSomeOperationWithTwoAutoclosure(2 > 3, op2: 3 > 2)

@autoclosure 本身取名也有体现出这种语法的意思。直译为自动闭包,也就是会把 (2 > 3) 这样的语法自动转换为闭包执行。

我们再来看看延迟执行这事。其实延迟这个特性,本身不是 @autoclosure 带来的,而是本来闭包本身就带有这样的特性。以上的 op1op2 都是在调用的时候才去执行。

@noescape 和 @escape

对于 autoclosure 属性来讲,还有2个相关的属性不得不提。也就是 @noescape@escape

这2个属性都是用来修饰闭包的。 @noescape 意思是非逃逸的闭包,而 @escape 则相反。

默认情况下,闭包是 @escape 的。表示此闭包还可以被其他闭包调用。比如我们常用的异步操作:

func executeAsyncOp(asyncClosure: () -> ()) -> Void {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
asyncClosure()
}
}

其中 asyncClosuredispatch_async 中的闭包中调用,完成异步的操作。因为闭包默认是 @escape 的,以上代码是可以运行的。但是当我们在 asyncClosure 前面加入 @noescape 属性时候,编译器就会报错:

closure use of @noesape parameter `asyncClosure` may allow it to escape

@noescape 属性是在 Swift 1.2 中引入的,把传入闭包参数的调用限制在调用的函数体内,对性能有一定的提升,同时将闭包标注为 @noescape 使你能在闭包中隐式地引用self。

在 Swift 标准库中很多方法,都用了 @noescape 属性,比如 Array 对应的方法 map,filter 和 reduce:

func map<T>(@noescape transform: (Self.Generator.Element) -> T) -> [T]

func filter(@noescape includeElement: (Self.Generator.Element) -> Bool) -> [Self.Generator.Element]

func reduce<T>(initial: T, @noescape combine: (T, Self.Generator.Element) -> T) -> T

@autoclosure 默认是 @noescape 的,要使用逃逸特性,请使用 @autoclosure(escaping)

用法

官方库中在这些地方用到了 @autoclosure

  • 断言, 官方博客的 介绍
    注意这篇文章很老了,Swift 1.2 中, @autoclosure 的语法形式已经改变了。最新的 @autoclosure 语法是把 @autoclosure 放到了参数名的前面,之前是放到了参数和类型中间的。
  • ?? 操作符, 参见喵神的 文章

同时在项目当中,一些比较耗时的操作,使用函数把操作封装起来,作为闭包传入到处理函数中。这时就可以用到 @autoclosure , 简化调用的形式,比如以下一个例子:

/* 在文件读或写操作后,做一些其他操作 */
func doSomeOpAfterFileOp(@autoclosure fileOp: () -> Bool) {
if fileOp() == true {
//做其他操作
}
}

func fileOp() -> Bool {
return true
}

doSomeOpAfterFileOp(fileOp())

stackoverflow 相关问题整理

  • How to use Swift @autoclosure : 如何使用 @autoclosure
  • @noescape attribute in Swift 1.2 : 关于 @noescape 的理解。
  • Variadic @autoclosure’s in Swift 1.2? :可变参数和 @autoclosure
  • How to use @autoclosure parameter in async block in Swift? : 异步调用和 @autoclosure

参考资料

  • 非逃逸闭包
  • 莫名其妙的标记之@noescape
  • Swift Param Attribute - @autoclosure why?

本文相关代码地址

原文  http://swift.gg/2016/04/06/swift-qa-2016-04-06/
正文到此结束
Loading...