为什么在Swift 3中默认情况下它们都是非转义时,闭包需要一个明确的`self`?

bei*_*ian 18 closures swift swift3

我注意到在Swift 2.2中,标记为非转义的闭包@noescape不需要显式self.在Swift 3中,默认情况下所有闭包都是非转义的,现在要求它们标记,@escaping如果你想让它们能够逃脱的话.

鉴于默认情况下Swift 3中的所有闭包都是非转义的,为什么它们需要显式self

final class SomeViewController: NSViewController {

    var someClosure: () -> () = { _ in }

    override func viewDidLoad() {
        super.viewDidLoad()

        someClosure = {
            view.layer = CALayer() // ERROR: Implicit use of `self` in closure; use `self.` to make capture semantics explicit
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Ham*_*ish 12

在Swift 3中,默认情况下所有闭包都是非转义的

不,在Swift 3中,只有闭包函数参数(即函数本身的函数输入)默认是非转义的(根据SE-0103).例如:

class A {

    let n = 5
    var bar : () -> Void = {}

    func foo(_ closure: () -> Void) {
        bar = closure // As closure is non-escaping, it is illegal to store it.
    }

    func baz() {
        foo {
            // no explict 'self.' required in order to capture n,
            // as foo's closure argument is non-escaping,
            // therefore n is guaranteed to only be captured for the lifetime of foo(_:)
            print(n)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

closure在上面的例子是非转义,它是从被存储或捕获的,因而限制了其使用寿命的功能的寿命禁止foo(_:).因此,这意味着它捕获的任何值都保证在函数退出后不会被捕获 - 这意味着您不必担心捕获时可能出现的问题,例如保留周期.

但是,闭包存储属性(例如bar在上面的例子中)是按照定义转义(用它来标记它@noescape没有意义的),因为它的生命周期不限于给定的函数 - 它(因此它所有捕获的变量)将保留在只要给定实例保留在内存中,就可以使用内存.因此,这很容易导致诸如保留周期之类的问题,这就是为什么需要使用显式self.来使捕获语义显式化的原因.

事实上,例如,您的示例代码将在viewDidLoad()被调用时创建一个保留周期,someClosure强烈捕获selfself强引用someClosure,因为它是一个存储属性.

值得注意的是,作为"存储函数属性始终转义"规则的扩展,存储在聚合中的函数(即具有关联值的结构和枚举)也总是转义,因为对此类聚合的处理没有限制.正如pandaren codemaster指出的,这当前包括Optional- 意味着Optional<() -> Void>(aka.(() -> Void)?)总是逃避.编译器最终可能会将此作为函数参数的一个特例,因为可选的已构建在很多编译器魔法上.


当然,您希望能够使用该@noescape属性的一个地方是一个闭包,它是函数中的局部变量.这样的闭包将具有可预测的寿命,只要它不存储在函数外部或被捕获.例如:

class A {

    let n = 5

    func foo() {

        let f : @noescape () -> Void = {
            print(n)
        }

        f()
    }
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,正如@noescape在Swift 3中删除的那样,这是不可能的(有趣的是,在Xcode 8 GM中,它是可能的,但会产生弃用警告).正如Jon Shier所说,我们将不得不等待它重新添加到语言中,这可能会也可能不会发生.


Jon*_*ier 5

存储的闭包在默认情况下被认为是转义,即使它们不是真的.没有办法将它们标记为非转义,因此我们会像这样被卡住,直到它们添加@noescape回语言,这可能会或可能不会.请参阅swift-evolution邮件列表中的此讨论.