【问题标题】:Understanding escaping closures Swift理解转义闭包 Swift
【发布时间】:2021-12-20 10:52:07
【问题描述】:

在转义闭包方面需要帮助。

我理解转义闭包的定义是If a closure is passed as an argument to a function and it is invoked after the function returns, the closure is escaping.

我相信在某些情况下需要转义闭包。

  1. 将外部变量设置为传递闭包

    我理解这一点,因为即使在函数返回后变量仍然存在。

  2. 异步操作

    诸如Dispatch.main.async{}Network Request 之类的东西。这是我不太明白的部分。我知道Dispatch.main.async{} 在不同的线程上运行,并且异步操作需要一段时间才能完成,因此名称为异步。但是,我无法理解这些操作如何超出函数的范围。

【问题讨论】:

    标签: ios swift escaping closures


    【解决方案1】:

    当您执行异步工作时,它可能转义当前范围原因,根据定义,它不会等待其完成。 这也是为什么术语completion用于那些闭包异步代码中用于传递结果的原因。

    这是一个示例,假设queueDispatchQueue 并且someHugeWork() -> R 是一个同步 函数,它返回一个结果R

    func doItSync() -> R {
        var result: R!
        queue.sync {
           result = someHugeWork()
        }
        // the function has to wait for the code submitted synchronously 
        // on the queue.
        
        return result
    }
    
    func doItAsync(completion: @escaping (R) -> Void) {
       queue.async {
         let result = someHugeWork()
         completion(result)
       }
       // work was submitted asynchronously on the queue, hence the function 
       // just returns, therefore the completion closure might escape this
       // scope.
    }
    

    现在,显然在这种情况下doItSync() 并没有真正意义,因为它会阻塞当前线程,直到someHugeWork() 在不同的队列上执行。这也是您可以从其作用域返回结果的原因:它等待它。我添加这个只是为了让您了解同步代码和返回结果的异步代码之间的区别。

    另一方面,doItAsync(completion:) 只是立即返回,而无需等待 someHugeWork() 在另一个队列上完成,因此它不会阻塞当前线程。
    由于您已在队列中异步 提交此工作,因此您现在无法控制何时执行,这也是为什么您从someHugeWork() 返回结果的唯一方法是使用 completion 闭包,它必须在 work item捕获 提交异步...如果您考虑一下就好像这样的 completion 闭包被存储以供队列在稍后执行 异步 提交的工作项时检索(您在问题中说过你确实有存储闭包的概念)。

    现在让我们也看看一个不需要 escaping 闭包的方法,这将是SequenceforEach(_:) 方法的body 参数:这个方法执行给定的关闭序列的每个元素,就像这样做:

    func forEach(_ body: (Element) -> Void) {
       for element in self {
          body(element)
       }
       // the function returns only after the for-in loop has completed,
       // hence body closure only executes in the scope of this method,
       // thus it never escapes.
    }
    

    (为简单起见,我省略了抛出注释)

    在这种情况下,body 闭包永远不会逃脱forEach(_:) 方法的作用域,因此它不需要注释为@escaping:它只会在for-in 循环内执行,该循环必须在方法之前完成可以返回。

    【讨论】:

      【解决方案2】:

      当您调用DispatchQueue.main.async{} 时,您正在创建一个发送到给定调度队列的闭包(Objective-C 中的block)。然后,您的函数将在 async 调用之后继续运行,并且可能在块有机会在 DispatchQueue 上运行之前结束。闭包“逃脱”了!

      当块到达其DispatchQueue的前面时,块中的代码实际上会运行

      async并不是说这个操作需要很长时间,而是这个函数(调用async的那个)不会暂停等待块完成。

      (如果您确实想暂停并等待操作完成,您可以调用sync 而不是async)。

      【讨论】:

      • 嗨斯科特!只是为了澄清“转义部分”。我对异步的理解是异步操作之后的操作将继续进行,而无需等待异步操作结束。所以在这种情况下,异步“生活在”函数之外,因为它被发送到不同的“地方”运行,而函数的以下部分继续运行?
      • 是的。您传递给async 的块被发送到的“不同位置”是DispatchQueue。在函数中调用async 之后的任何内容都将继续运行。因此,闭包“转义”了函数体的上下文。
      猜你喜欢
      • 2022-01-09
      • 2018-05-05
      • 2017-01-23
      • 1970-01-01
      • 2017-01-29
      • 2020-05-04
      • 2015-03-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多