为什么 Swift 3 需要 @escaping 注释?

mix*_*xel 5 closures swift swift3

我阅读了这个问题和答案以及Cocoacasts 博客文章,我完全理解什么是@escaping注释。

但老实说,我不明白我们为什么需要它。

上面的 Cocoacasts 博客文章指出:

默认情况下使闭包不转义有几个好处。最明显的好处是性能和编译器优化代码的能力。如果编译器知道闭包是非转义的,它可以处理内存管理的许多细节。

但是 Swift 编译器可以确定是否@escaping丢失并在这种情况下显示错误,所以如果我们@escaping从 Swift 语言中删除注释,那么编译器仍然可以看到闭包何时没有逃逸,我们可以让他在这种情况下应用优化。

这也意味着您可以在非转义闭包中使用 self 关键字而不会出现问题,因为闭包在函数返回之前被调用。没有必要在闭包中使用对 self 的弱引用。这是您免费获得的一个很好的好处。

但是如果闭包参数被标记为@escaping我仍然可以传递使用强引用的闭包self并且编译器不会显示任何警告。实际上,如果所有在@escaping闭包中捕获的引用都是weak默认的,并且应用特殊的关键字来增强它们,那将会更有用。

此外,我认为@escaping注释可能是通过明确声明此闭包参数不会转义函数体来使代码自记录的方法,但是调用方的目的是什么?它不限制闭包定义的方式,也不阻止调用方使用强引用。所以我剩下的就是希望调用方仔细查看函数签名并采取适当的措施(例如使用弱引用)。

所以问题是为什么我们真的需要@escaping在 Swift 3 中使用闭包,以及在哪些情况下我们不能没有它?

更新

我知道不能将不转义的闭包传递给闭包参数标记为的函数@escaping

func testNoEscape(f: () -> ()) {
    f()
}

var storeF: (() -> ())?

func testEscaping(f: @escaping () -> ()) {
    storeF = f
}

func tryPassNoEscapeToEscaping(f: () -> ()) {
    testEscaping(f: f)
}
Run Code Online (Sandbox Code Playgroud)

导致编译错误:

passing non-escaping parameter 'f' to function expecting an @escaping closure
Run Code Online (Sandbox Code Playgroud)

但这是@escaping闭包带来的唯一真正限制,它看起来像是围绕自身构建的,并没有带来任何其他好处。

更新 2

虽然我上面的想法是正确的,但我的最后一个问题是不准确的。

真正的问题是,@escaping如果编译器可以自己检测转义闭包并且@escaping注释不对参数值施加任何限制,为什么我们需要注释?

在我看来,如果编译器不允许我们在转义闭包(例如 usingself和其他强引用)方面做一些坏事,那将会有用得多。或者,如果转义闭包是某种特殊类型,我们必须在调用带有转义闭包参数的函数时提到这一点:

func f(c: () -> ()) { // c is escaping from f somehow
    // ...
}

f escaping { // have to use `escaping` keyword 
    // ...
}
Run Code Online (Sandbox Code Playgroud)

所以调用方不必查看f签名就知道它c正在转义,因为如果它尝试将非转义闭包作为转义闭包参数值传递,它将得到编译错误。

在当前的实现中,想要f在他们的代码中使用的开发人员必须查看f签名以了解c将逃脱这是不安全的,因为这要求最初编写此代码并稍后修改它的任何人都必须知道f签名的细节,这是不可靠的等等代码不是自我记录的。

我知道我的问题可能不适合 SO。对于那个很抱歉。

如果是这样,如果我不会从使用 Swift 语言和编译器实现转义逻辑的人那里得到答案,我将在稍后关闭它。

mat*_*att 5

非转义闭包可以做转义闭包不能做的事情。它们是完全不同的动物。

例如,一个非转义的闭包可以在self没有明确说明的情况下引用 的属性self。这是因为,由于是非转义的(即它在收到后立即执行),它没有self以某种棘手的方式捕获并导致保留循环的危险。

而且,非转义闭包可以关闭inout参数。但这对于转义闭包是没有意义的,并且是不允许的。

如果一个闭包有时需要self有时不需要self有时允许关闭inout有时又不存在,那将是非常神秘的。该@escaping注解使得这样的规则区分明确和一贯的。


Ale*_*ica 2

也许还有其他原因,但最主要的原因是要传达这样的信息:您的闭包将比您传递给它的函数的持续时间更长。这可能会对程序的行为产生非常复杂的影响,因此弄清楚这一点很重要。

此外,闭包的“转义性”是公共 API 的一部分。编译器无法查看正在调用的已编译库。如果没有该@escaping属性公开传达闭包无法转义的事实,编译器就无法从编译的代码中自行推断出它。