【问题标题】:How completion handler work under the hood [closed]完成处理程序如何在后台工作[关闭]
【发布时间】:2020-02-21 10:23:05
【问题描述】:

我试图了解如何使用异步代码调用完成

使用同步代码,很简单,我们从上到下逐行运行代码,如下所示:

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())
}

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

【问题讨论】:

    标签: swift closures completion


    【解决方案1】:

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

    这里,completionHandler 是一种在 Swift 中称为闭包的值类型。它的行为有点像 lambdas/匿名函数/等。 (“种类”是指它如何与 Swift 的垃圾收集 ARC 配合使用,ARC 与其他流行语言的工作方式不同。不过,这是一个单独的话题。)。

    这些闭包本质上是函数指针,所以你基本上是在注入一个函数作为可以在你的函数中使用的参数。

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

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

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

    评论回复:

    非常感谢,但我还有一个问题:你说“如果你的线程阻塞工作正常”,但在一般情况下,会发生吗?我的意思是,如果我对阻塞线程、添加断点什么都不做,……会发生吗? - 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)
    }
    

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

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

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

    最好只捕获完成处理程序{[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)
        }
    }
    

    【讨论】:

    • 非常感谢,但我还有一个问题:你说“如果你的线程阻塞工作正常”,但一般情况下会发生吗?我的意思是,如果我对阻塞线程、添加断点不做任何事情……会发生吗?
    • 我将通过编辑更新我的答案 ^
    • 多么好的答案!你是我的英雄:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-31
    • 1970-01-01
    • 2011-01-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多