完成处理程序如何在引擎盖下工作

How completion handler work under the hood

我正在尝试了解如何使用异步代码调用完成

有了同步代码,就简单了,我们有运行个代码,从上到下,一行到一行,像这样:

func work(completion: () -> Void) {
    // do work 1 
    // do work 2
    completion()
}

但是需要时间的任务?我认为它就像上面的例子一样工作,让我们考虑下面的例子。在这里,我模仿 dataTask 函数:

func dataTask(url: URL, completionHandler: @escaping (URLResponse?) -> Void) {
    preparing parameters
    sendingRequest // -> for example, it took 10s
    // -> Here, the thread blocked for waiting the response from server
    // After received the response from server (10s), thread unblocked and run the next line
    completionHandler(URLResponse())
}

这就是我认为完成处理程序的工作方式。这是正确的?如果我错了,你能给我解释一下吗?

是的,如果您的线程阻塞正常工作(即断点在所有异步操作完成后命中该行),这就是它的工作原理。

这里,completionHandler是Swift中称为闭包的值类型。它的行为有点像 lambdas/anonymous functions/etc。 ("kind of" 指的是它如何与 Swift 的垃圾收集器 ARC 一起玩,ARC 的工作方式与其他流行语言不同。不过那是一个单独的主题。)。

这些闭包本质上是函数指针,因此您基本上是将函数作为参数注入,可以在您的函数中使用。

换句话说,这就像为您的函数提供一个带有按钮的框。只要它可以提供必要的输入,您的功能就可以随时按下该按钮。在这种情况下,需要 URLResponse? 才能按下该按钮。当按下那个按钮时,这个函数的调用者将执行它定义的任何代码块(同步地,或者在将来的某个时候)。

因为你的问题涉及"under the hood" 我建议阅读一些 Swift [文档][1]。如果有什么不明白的地方,请继续在这里发表评论。其他人花了几个小时用我无法在这里合理模仿的大量细节和行为来完善该页面。

专业提示:不要忘记使用 [weak self]!!!

评论回复:

thank you very much, but I still have one more question: you said "if your thread blocking works correctly", but in ordinary case, will it happen? I mean, if I do nothing about blocking thread, adding break point,... is it happen? - phuongzzz

我们来看这个例子:

//You can copy-paste the following into a playground
import Foundation

//MARK:- FUNCTIONS

//Here's a buggy function
func intentionallyBuggyFunction(onDone: @escaping (String) -> ())
{
    var resultTxt = "Oops the async did not happen"

    DispatchQueue.main.asyncAfter(deadline: .now() + 3)
    {
        resultTxt = "The async happened!"
    }
    onDone(resultTxt)
}
//Here's one that'll async properly (this example is just for demonstration purposes)
func thisOneWillWork(onDone: @escaping (String) -> ())
{
    let thread = DispatchQueue(label: "Bambot", qos: .userInteractive, attributes: .concurrent,
                               autoreleaseFrequency: .workItem, target: nil)
    thread.async {[onDone]
        var resultTxt = "Oops the async did not happen"
        let dg = DispatchGroup()

        dg.enter()
        thread.asyncAfter(deadline: .now() + 3)
        {[weak dg] in
            resultTxt = "The async happened!"
            dg?.leave()
        }
        dg.wait()
        onDone(resultTxt)
    }
}

//MARK:- CODE EXECUTION

//This'll print the wrong result
intentionallyBuggyFunction { (resultStr) in
    print(resultStr)
}
//This'll print the right result
thisOneWillWork { (resultStr) in
    print(resultStr)
}

如您所见,第一个函数将异步内容排队,立即执行闭包,然后命中其结束函数大括号。即使闭包正在转义,该函数也会在异步内容发生之前完成执行。

第二个函数实际上指示设备在单独的线程上等待挂起的更新。这样,在调用闭包之前更新字符串。

现在,我认为第二个功能是意大利面条代码。像这样随心所欲地启动 DispatchQueue 并不是一个好习惯,因为 iOS 池中只有那么多线程可供选择。它还在滥用 DispatchGroup(对于这样的行为,您应该创建一个串行队列(您可以 Google 那个))。

最好只捕获完成处理程序 {[onDone](...) in 然后在实际的异步块中调用它,如下所示:

//Better async (cleaner, makes more sense, easier to read, etc.)
func betterAsync(onDone: @escaping (String) -> ())
{
    var resultTxt = "Oops the async did not happen"

    DispatchQueue.main.asyncAfter(deadline: .now() + 3)
    {[onDone] in
        resultTxt = "The async happened!"
        onDone(resultTxt)
    }
}