【问题标题】:Swift 3 :Closure use of non-escaping parameter may allow it to escapeSwift 3:关闭使用非转义参数可能会允许它转义
【发布时间】:2017-07-02 01:48:12
【问题描述】:

我有以下函数,我有完成处理程序,但我收到此错误:

Closure use of non-escaping parameter may allow it to escape

这是我的代码:

func makeRequestcompletion(completion:(_ response:Data, _ error:NSError)->Void)  {
    let urlString = URL(string: "http://someUrl.com")
    if let url = urlString {
        let task = URLSession.shared.dataTask(with: url, completionHandler: { (data, urlRequestResponse, error) in
            completion(data, error) // <-- here is I'm getting the error
        })
    task.resume()
    }
}

你们中有人知道我为什么会收到这个错误吗?

非常感谢您的帮助

【问题讨论】:

标签: ios swift xcode8 completionhandler


【解决方案1】:

看起来你需要明确定义允许闭包逃逸。

来自Apple Developer docs

当闭包作为参数传递给函数时,闭包被称为转义函数,但在函数返回后被调用。当你声明一个将闭包作为其参数之一的函数时,你可以在参数的类型前写上@escaping 来表示允许闭包转义。

TLDR;在完成变量后添加@escaping关键字:

func makeRequestcompletion(completion: @escaping (_ response:Data, _ error:NSError)->Void)  {
    let urlString = URL(string: "http://someUrl.com")
    if let url = urlString {
        let task = URLSession.shared.dataTask(with: url, completionHandler: { (data, urlRequestResponse, error) in
            completion(data, error) // <-- here is I'm getting the error
        })
        task.resume()
    }
}

【讨论】:

  • 为什么要@escaping?
  • 现在在 Swift 3 中,您必须明确定义函数何时包含在函数被调用(转义)后执行的完成处理程序。苹果说,“函数在开始操作后返回,但直到操作完成才调用闭包——闭包需要转义,稍后再调用......如果你没有标记 this 的参数@escaping 的函数,你会得到一个编译时错误。"
  • 只是顺便感谢@MarkBarrasso 的解释。一直在阅读但无法理解整个“逃避”的事情,这个评论让我看到了曙光;)
  • 谢谢@MarkBarrasso,你的回答对我很有帮助。
【解决方案2】:

“转义”闭包是一种可以比创建它的范围更长的闭包。转义闭包需要特别注意引用计数和内存管理,并且可能更难优化。

在 Swift 3 之前,闭包的默认设置是假设它们正在转义。这意味着开发人员必须专门识别已知的闭包以允许编译器进行优化。社区发现,事实上,编译器可以很容易地自行发现闭包是否正在转义,并认为积极的转义方法可能会导致代码更快。结果是现在假定闭包是非转义的,您需要使用@escaping 属性标记正在转义的闭包。

在你的情况下,URLSession.shared.dataTask 接受的闭包本身就是一个转义闭包,所以如果你在其中使用闭包,它也需要标记为@escaping

【讨论】:

  • @Linasses completion: @escaping (_ response:Data, _ error:NSError)-&gt;Void.
  • @escaping 错误弹出虽然我已经有@escaping :| open static func resumeSession(_ completion: @escaping (Result) -> (Void)) {@ escaping error pop-up 虽然我已经有@escaping :| open static func resumeSession(_ completion: @escaping (Result) -> (Void)) {
  • 鉴于编译器可以检测到何时需要添加@escaping(即它会产生错误),这不能简单地对开发人员保持透明,并且编译器仍然可以优化引擎盖?以这种方式明确表达有什么好处?
  • @StephenT, @escaping 是函数二进制接口的一部分,就像闭包本身的类型一样。编译器在传递不转义的闭包时会生成更好的代码,但如果闭包确实转义,则该代码会导致不可预知的结果(可能会崩溃)。如果您分发了一个二进制框架并且没有注释闭包,那么更新可能会在不知不觉中破坏调用代码。至少,通过显式注释,当您更改转义行为时,它会迫使您考虑自己在做什么,这不会是偶然或意外发生的。
【解决方案3】:

@escaping 对所有调用方法都具有传染性,编译器会确定您何时必须包含它。

考虑这个例子(编译):

dispatchSometime( { print("Oh yeah") })

func dispatchSometime(_ block: ()->()) {
    dispatchNow(block)
}

func dispatchNow(_ block: ()->()) {
    block()
}

然而,这个修改后的示例会产生 两个 non-escaping parameter may allow it to escape 类型的错误:

dispatchSometime( { print("Oh yeah") })

func dispatchSometime(_ block: ()->()) {
    dispatchLater(block)
}

func dispatchLater(_ block: ()->()) {
    DispatchQueue.main.async(execute: block)
}

main 上的调度意味着dispatchLater 方法需要@escaping,一旦你添加了,dispatchSometime 方法需要@escaping 来编译示例。

dispatchSometime( { print("Oh yeah") })

func dispatchSometime(_ block: @escaping ()->()) {
    dispatchLater(block)
}

func dispatchLater(_ block: @escaping ()->()) {
    DispatchQueue.main.async(execute: block)
}

然而,收获只是:

  • 继续在调用链上添加@escaping,直到编译器停止抱怨。
  • 关键字不会改变任何东西:它是一个警告,本质上是“小心使用 weak 捕获的变量,因为它们可能与块本身一起保留。”

含义

真正有趣的情况是,您必须调整几个方法以包含 @escaping 关键字,这会使编译器停止抱怨。但是,如果这些方法实际上符合某个协议,则该协议的方法必须获得@escaping 关键字,这也会感染所有其他符合协议的协议。好玩!

【讨论】:

    猜你喜欢
    • 2016-12-23
    • 2020-02-04
    • 2019-02-19
    • 2019-01-05
    • 2021-02-03
    • 2017-03-08
    • 2020-05-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多