iosswiftcompletionhandlerdispatch-queueios-multithreading

Completion handler called twice (with threading)


I'm currently testing this code in the Xcode 10 playground (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()

The problem is that both "hi" and "nil" are printed.

If I get rid of the threading, it works fine but with it it seems like it gets to the second DispatchQueue.main.async before the first has the chance to return.

There's a lot more stuff going on in the "Some slow stuff" if in my actual code but I can't rely on that taking long enough to return before the second return is called too.

How do I accomplish this: Have the function run in a background thread but return only once on the main thread (like code without threading normally would)?


Solution

  • I believe your goal is to only call the completion handler once, and when you do you are done. In that case, call return in the .background thread after queueing the completion call on the main thread:

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