Swift 1.2中的@noescape属性

75
在Swift 1.2中,函数中的闭包参数有了一个新属性,正如文档所述:
这表明该参数仅被调用(或在调用中作为@noescape参数传递),这意味着它不能超出调用的生命周期。
在我看来,在此之前,我们可以使用[weak self]来防止闭包对其类产生强引用,而此时闭包被执行时self可能为空或是实例。但现在,@noescape表示如果类被析构,闭包将永远不会被执行。我理解得对吗?
如果我理解正确,那么为什么要使用@noescape闭包而不是常规函数,它们的行为非常相似?
3个回答

143

@noescape 可以这样使用:

func doIt(code: @noescape () -> ()) {
    /* what we CAN */

    // just call it
    code()
    // pass it to another function as another `@noescape` parameter
    doItMore(code)
    // capture it in another `@noescape` closure
    doItMore {
        code()
    }

    /* what we CANNOT do *****

    // pass it as a non-`@noescape` parameter
    dispatch_async(dispatch_get_main_queue(), code)
    // store it
    let _code:() -> () = code
    // capture it in another non-`@noescape` closure
    let __code = { code() }

    */
}

func doItMore(code: @noescape () -> ()) {}

添加@noescape可以保证闭包不会被存储在其他地方,也不会在以后的时间或异步使用。

从调用者的角度来看,不需要关心捕获变量的生命周期,因为它们仅在被调用的函数内部使用或根本不使用。而且,作为额外的优势,我们可以使用隐式的self,省去了输入self.的步骤。

func doIt(code: @noescape () -> ()) {
    code()
}

class Bar {
    var i = 0
    func some() {
        doIt {
            println(i)
            //      ^ we don't need `self.` anymore!
        }
    }
}

let bar = Bar()
bar.some() // -> outputs 0

此外,从编译器的角度来看(根据发布说明中的记录):

这可以实现一些微小的性能优化。


5
对我来说最关键的一点是: "@noescape 保证闭包不会被异步使用"。这意味着你不能将其与触发异步操作的网络代码一起使用。 - David James

28

有一种思考方式是,在@noescape块内的每个变量都不需要是Strong(不仅仅是self)。

还有一些优化可能,因为一旦分配了一个变量并将其包装在块中,它就不能在函数结束时被普通地释放。所以它必须在堆上分配,并使用ARC来析构。在Objective-C中,您必须使用“__block”关键字来确保变量以块友好的方式创建。Swift会自动检测到这一点,因此不需要关键字,但成本相同。

如果变量被传递给@noescape块,则它们可以是堆栈变量,无需使用ARC进行解除分配。

现在,这些变量甚至不需要是零引用弱变量(比不安全指针更昂贵),因为它们将保证在块的生命周期内处于“活动”状态。

所有这些都会导致更快、更优化的代码,并减少使用@autoclosure块的开销(非常实用)。


你能提供一些参考资料吗?我对你在这里所说的内容非常感兴趣。 - Dániel Nagy

8
(关于Michael Gray上面的回答。)不确定这是否特别适用于Swift,或者甚至Swift编译器是否充分利用了它。但是,如果编译器知道被调用的函数不会尝试在堆中存储该实例的指针,那么标准编译器设计将在堆栈上为实例分配存储空间,并在函数尝试这样做时发出编译时错误。这对于传递非标量值类型(如枚举、结构体、闭包)特别有益,因为复制它们可能比仅传递指向堆栈的指针要昂贵得多。分配实例也显着更少费用(一个指令而不是调用malloc())。因此,如果编译器可以进行这种优化,那么就是双倍的胜利。同样,无论Swift编译器的哪个版本是否实际执行此操作都必须由Swift团队声明,或者您必须在他们开源代码时阅读源代码。从上面的引用中关于“次要优化”的说法来看,听起来要么不是这样,要么Swift团队认为这是“次要”的。我认为这是重要的优化。据推测,该属性存在是为了使编译器能够执行此优化(至少在未来)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接