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

时间:2017-03-01 11:54:27

标签: swift swift3 closures

我读了这个question and answersCocoacasts blog post,我完全理解什么是@escaping注释。

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

上述Cocoacasts博客文章指出:

  

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

但Swift编译器可以确定是否缺少@escaping并在这种情况下显示错误,因此如果我们从Swift语言中删除@escaping注释,那么编译器仍然可以看到何时关闭不会逃脱,我们可以在这种情况下让他应用优化。

  

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

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

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

所以问题是为什么我们真的需要在Swift 3中关闭@escaping以及在没有它的情况下我们不能做什么呢?

更新

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

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

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

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

func tryPassNoEscapeToEscaping(f: () -> ()) {
    testEscaping(f: f)
}

导致编译错误:

passing non-escaping parameter 'f' to function expecting an @escaping closure

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

更新2

虽然我正确地阐述了我的想法,但我的最后一个问题是不准确的。

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

在我看来,如果编译器不允许我们在使用self和其他强引用之类的转义闭包中做一些坏事,那将会更有用。或者,如果转义闭包是一些特殊类型,我们必须提到在使用转义闭包参数调用函数时:

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

f escaping { // have to use `escaping` keyword 
    // ...
}

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

在当前的实现中,想要在代码中使用f的开发人员必须查看f签名,以了解c将逃脱哪个不安全,因为这需要任何人写这个代码最初并在以后修改它必须知道f签名的详细信息,这是不可靠的,而且这些代码不是自我记录的。

我明白,也许我的问题不适合SO。对不起。

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

3 个答案:

答案 0 :(得分:3)

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

例如,非转义闭包可以引用lua_State* L = luaL_newstate(); luaL_openlibs(L); lua_State* thread1 = lua_newthread(L); lua_State* thread2 = lua_newthread(L); // load chunk as a function and copy it to the top of each thread's stack luaL_loadfile(L, "luac1.bin"); lua_pushvalue(L, -1); lua_xmove(L, thread1, 1); lua_pushvalue(L, -1); lua_xmove(L, thread2, 1); lua_resume(thread1, NULL, 0); lua_resume(thread2, NULL, 0); 的属性而不明确说self。这是因为,在非转义(即收到后立即执行)时,它不会以某种棘手的方式捕获self并导致保留周期。

并且,非转义闭包可以关闭self参数。但这对于逃避关闭来说毫无意义,并且是不允许的。

如果一个闭包似乎有时需要inout有时不需要self有时< / em>允许关闭self有时不关闭。 inout注释使得这种规则区分清晰且一致。

答案 1 :(得分:2)

也许还有其他原因,但主要原因是沟通你的闭包将比传递它的函数的持续时间更长。这可能会对您的计划行为产生非常复杂的影响,因此明确这一点非常重要。

此外,&#34;逃避&#34;闭包是公共API的一部分。编译器无法查看正在调用的已编译库。如果没有@escaping属性公开传达闭包不能逃脱的事实,编译器就无法从编译的代码中为自己推断出它。

答案 2 :(得分:0)

编译器可以看到您创建的代码,但无法看到您作为预编译框架的一部分使用的代码(截至目前,由于ABI问题,没有Swift框架,但是会有在将来)。

也许他们可以将这种逃避要求仅应用于公共&#34;功能,但看起来有点不一致。最好只通过工具提示插入关键字。