为啥 @noescape 在需要时不会自动应用于 Swift 闭包?

Posted

技术标签:

【中文标题】为啥 @noescape 在需要时不会自动应用于 Swift 闭包?【英文标题】:Why is not @noescape automatically applied to Swift closures when it is needed?为什么 @noescape 在需要时不会自动应用于 Swift 闭包? 【发布时间】:2016-01-12 20:43:59 【问题描述】:

我想知道为什么标签noescape 没有被自动检测到并且需要明确应用。

事实上,它在编译时以某种方式被检测到,因为尝试将@noescape 标记添加到带有转义闭包的 func 会导致错误。

所以问题是为什么...为什么需要明确添加 noescape 而 Apple 没有创建它以便在需要时自动添加?

【问题讨论】:

谁说它不适用?例如,如果编译器可以访问函数体(参见 this SO question),则默认应用函数内联,因此其他可能的优化也可能发生这种情况。 好吧,我可能会根据语法做出错误的假设,但要注意使用闭包中的类属性确实需要使用self,除非它被明确标记为@noescape。我会说它不会自动应用。 编译器的构建是为了利用所有可能的优化。 @noescape 更多地用于仅带有标头的库,编译器不知道函数体内发生了什么。通过访问函数的源代码,编译器能够判断它是否可以应用 @noescape 可能带来的优化(在我的头顶副本/闭包上发布) 【参考方案1】:

编辑: Swift 3 在此做了一些更改:

Non-escaping closures are the default. 现在,如果您需要非转义闭包,则不必将@noescape 应用于将闭包作为参数的函数声明。 (相反,如果您确实计划在函数返回之后存储闭包,则必须申请 @escaping。) 闭包is now part of the function type declaration 的“转义性”。所以不是看起来像@escaping completionHandler: (Bool, Error) -> Void 的参数,而是completionHandler: @escaping (Bool, Error) -> Void

重写我的整个答案以反映这一点有点困难,所以我暂时将其留在这里...继续阅读为什么 转义性背后的原因,只需记住反转 noescape/转义声明。 :) 或者阅读 The Swift Programming Language 的 Swift 3 版本中的 Escaping Closures。


@noescape 不仅仅是编译器优化的提示;它是函数声明呈现给调用者的接口的一部分。函数的参数是声明为 @noescape 还是允许转义闭包会改变该函数的调用者如何编写它们作为参数传递的闭包。

例如,给定函数(来自SequenceType):

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

如果我想根据需要在 self 上调用方法的某些条件过滤集合,我知道我可以安全地做到这一点,而不必担心闭包是否会捕获 self 并创建一个保留周期。

// horribly contrived example
class Foo 
    var things: [Thing]
    func isCurrentlyAwesomeThing(thing: Thing) -> Bool  /*...*/ 
    func thingsThatAreAwesomeRightNow() -> [Thing] 
        return things.filter 
            return isCurrentlyAwesomeThing($0)
        
    

如果filter 允许转义闭包,则调用isCurrentlyAwesomeThing() 的闭包将捕获self。 (因此需要使用显式 self. 前缀调用该方法。)如果 filter 的实现实际上在该函数的运行时之外保存了闭包,则会出现内存泄漏,因为闭包保留了 @987654340 @ 和 self 保留 filter 函数收到闭包的数组。

当你调用一个没有声明闭包参数@noescape的函数时,你必须考虑到这种可能性。这就是为什么您会看到调用添加了 [weak self] 捕获列表和闭包内的强重新声明,以确保 self 在闭包期间不会被释放。 (或者至少一个[unowned self],如果你有理由确定关闭不会持续超过self。)

如果闭包参数不能被装饰成@noescape(或者因为没有那个装饰器而不能转义),你就不会知道在调用一个需要闭包的函数时是否需要小心关于该闭包捕获的内容。

【讨论】:

【参考方案2】:

如果你使用任何函数参数作为 noescape,你不能存储在另一个闭包中,你不能使用 dispatch_asynch,你不能从 noescape 闭包调用非 noescape 闭包

在那个闭包中应该包含的 noescape 属性的范围

【讨论】:

以上是关于为啥 @noescape 在需要时不会自动应用于 Swift 闭包?的主要内容,如果未能解决你的问题,请参考以下文章

当我将 Django 应用程序部署到 Heroku 时,为啥 collectstatic 不会自动运行?

为啥属性不会自动合成

Swift:在使用过滤器函数进行过滤后,调用 indexOf 时无法将类型的值转换为 @noescape

为啥python不会在启动时自动导入每个模块?

为啥谷歌地图在首次启动时不会自动缩放到用户位置?

为啥我的 iOS 应用会自动启动?