完成处理程序调用两次(带线程)

Completion handler called twice (with threading)

我目前正在 Xcode 10 游乐场 (Swift 5) 中测试此代码:

func one() {
    let test = "bla"
    two(test, completion: { (returned) in
        print(returned)
        })
}

func two(_ test: String, completion: @escaping (_ returned: String?) -> Void) {
    DispatchQueue.global(qos:.background).async {
        if !test.isEmpty {
            //Some slow stuff
            DispatchQueue.main.async {
                return completion("hi!")

            }
        }

        //Also some slow stuff
        DispatchQueue.main.async {
            return completion(nil) //can't have this in "else"!
        }
    }
}

one()

问题是 "hi" 和 "nil" 都被打印了。

如果我摆脱线程,它工作正常但它似乎在第一个有机会 return.

之前到达第二个 DispatchQueue.main.async

在我的实际代码中 "Some slow stuff" if 中发生了很多事情,但我不能指望在第二个 [= 之前​​ return 花费足够长的时间27=] 也被调用。

我该如何实现:在后台线程中使用函数 运行,但在主线程中仅使用一次 return(就像没有线程的代码通常那样)?

为什么它被调用两次很明显。 (顺便说一句,你没有 return 完成块)你写的是这样的:

func two(_ test: String, completion: @escaping (_ returned: String?) -> Void) {
    DispatchQueue.global(qos:.background).async {
        if !test.isEmpty {
            //Some slow stuff
            DispatchQueue.main.async {
                completion("hi!")

            }
        }
        completion(nil) //can't have this in "else"!
    }
}

并且由于主线程始终保持运行,而您在后台威胁上做其他事情,您将获得 2 次完成。我猜你想做的是删除第二个?

另一种方法是创建一个 DispatchGroup 并在其中输入每个调用,然后编写一个 Dispatch wait till done 以等待所有请求完成

我相信你的目标是只调用一次 completion 处理程序,当你这样做时你就完成了。在那种情况下,在主线程上排队完成调用后,在 .background 线程中调用 return

func two(_ test: String, completion: @escaping (_ returned: String?) -> Void) {
    DispatchQueue.global(qos:.background).async {
        if !test.isEmpty {
            //Some slow stuff

            // notify main thread we're done
            DispatchQueue.main.async {
                completion("hi!")
            }

            // we are done and don't want to do more work on the
            // background thread
            return
        }

        //Also some slow stuff
        DispatchQueue.main.async {
            completion(nil)
        }
    }
}

您可以使用defer 语句来return 完成所有If 语句之后的块。这里只是您的代码示例,但我希望它很清楚。

func two(_ test: String, completion: @escaping (_ returned: String?) -> Void) {

        DispatchQueue.global(qos:.background).async {

            var resultString: String? 

            // Called only once after all code inside this async block.
            defer {
                DispatchQueue.main.async {
                    completion(resultString)
                }
            }

            if !test.isEmpty {

               //Some slow stuff
               resultString = "hi"

               return 
            }

            // Another stuff 
            resultString = nil
        }
    }