将闭包更新为Swift 3 - @escaping

75

我已将代码更新至 Xcode 8.0 beta 6,但卡在了似乎与新的非转义闭包默认值有关的地方。在以下代码中,Xcode 建议在下面代码的第一行前面添加 @escaping 以解决问题,但仍无法编译并进入循环。*

(编辑: 实际上,应该在 completion: 后面添加 @escaping,正如 Xcode 所建议的那样。警告可能仍会显示,但清理和编译将其删除。)* 如何重新编写 / 修复此代码以使其在更新的 Swift 3 中工作?我在新手册中查看了,但找不到适当的代码示例。

func doSomething(withParameter parameter: Int, completion: () -> ()) {
    // Does something

    callSomeOtherFunc(withCompletion: completion)
  }

// Calling the method and execute closure 
doSomething(withParameter: 2) {
  // do things in closure
}

非常感谢任何帮助!

2个回答

58

Swift 3:闭包参数属性现在应用于参数类型,而不是参数本身

在Swift 3之前,闭包属性@autoclosure@noescape曾经是闭包参数的属性,但现在是参数类型的属性;请参见以下已接受的Swift演变提案:

您具体的问题涉及参数类型属性@escaping(适用于同样的新规则),如在已接受的Swift演变提案中所述,让闭包参数默认为非逃逸:

这些提议现在都已经在Xcode 8 beta阶段实施(请参见Xcode 8 beta 6发布说明;需要开发者账户登录才能访问)
引用块: - 新版Swift编译器:Swift语言:闭包参数默认为非逃逸,而不是显式标注为@noescape。使用@escaping表示闭包参数可能会逃逸。@autoclosure(escaping)现在写作@autoclosure @escaping。标注@noescape@autoclosure(escaping)已被弃用。(SE-0103) - 新版Xcode 8 beta - Swift和Apple LLVM编译器:Swift语言:现在必须在参数类型之前而不是参数名称之前编写@noescape@autoclosure属性。[SE-0049]
因此,您需要使用非默认的@escaping属性,应用于闭包参数的类型,而不是参数本身。
func doSomething(withParameter parameter: Int, completion: @escaping () -> ()) {
    // ...
}

(包括我的回答在一个被点赞的评论中,因为评论不是SO上的持久数据)

@Cristi Băluță: "什么是escaping?在swift3自动转换之前从未见过这个关键字..."

请参考SE-0103进化提案的链接(以及来自beta 6发行说明的引用文本):以前,闭包参数默认为逃逸(因此不需要存在显式注释以进行逃逸),但现在默认为非逃逸。因此增加了@escaping来明确注释闭包参数可能会逃逸(与其默认行为相反)。这也解释了为什么@noescape现在已经被弃用(不需要注释默认行为)。

要解释闭包参数逃逸的含义,我引用了语言参考 - 属性

在方法或函数声明中,将此属性应用于参数的类型,以表明参数的值可以存储以供稍后执行。这意味着该值被允许超出调用的生命周期。

@nontomatic 很高兴能够帮助你。 - dfrib
7
逃逸(escaping)是什么意思?在进行Swift3自动转换之前从未见过这个关键词,我认为我没有失去任何东西。 - Cristi Băluță
@CristiBăluță 请参考上面的链接 SE-0103 evolution proposal(以及来自beta 6发布说明的引用文本):以前,闭包参数默认情况下是逃逸的(因此不需要注释它们是逃逸的),但现在则是非逃逸的。因此,添加了@escaping以明确注释闭包参数可能会逃逸(与其默认行为相反)。这也解释了为什么@noescape现在已经被弃用(不需要注释默认行为)。 - dfrib
3
@SagarR.Kothari 这个问题和答案都基于我们知道逃逸闭包和非逃逸闭包的区别,因此我之前对 CristiBăluță 的评论回答了为什么现在存在这个关键字的问题。为了解释它的作用,我引用语言参考手册的话:_"将此属性应用于方法或函数声明中参数的类型,以表明参数的值可以被存储以供以后执行。这意味着该值允许超出调用的生命周期。"_ - dfrib
为什么如果闭包参数是可选的,我不必将其标记为@escaping? - the Reverend
显示剩余4条评论

23

@noescape

从 xcode 8 beta 6 开始,@noescape 是默认值。在此之前,@escaping 是默认值。从以往版本升级到 swift 3.0 的用户可能会遇到此错误。

不能将 @noescape 的闭包储存在变量中,因为如果可以将闭包储存在变量中,那么您可以从代码的任何位置执行该闭包。但是,@noescape 指定闭包参数不能逃离函数体。

这将在 Xcode 8 中导致编译器错误。

class MyClass {

    var myClosure: (() -> ())?

    func doSomething(finishBlock: () -> ()) {
        myClosure = finishBlock    // ‼️ Error: Assigning non-escaping parameter 'finishBlock' to an @escaping closure
    }
}

这将编译正常(明确地写出@escaping

class MyClass {

    var myClosure: (() -> ())?

    func doSomething(finishBlock: @escaping () -> ()) {
        myClosure = finishBlock
    }
}

@noescape的好处:

  • 编译器可以优化您的代码以获得更好的性能
  • 编译器可以处理内存管理
  • 在闭包中不需要使用弱引用来引用self


详情请查看:使非逃逸闭包成为默认值


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