3

I have a function doEverything that takes a completion block. It calls two other functions, doAlpha and doBeta which both have completion blocks. These two functions should run asynchronously. I want to call doEverything's completion block after both of the other functions have called their completion blocks.

Currently, it looks like this:

func doEverything(completion: @escaping (success) -> ())) {
    var alphaSuccess = false
    var betaSuccess = false

    doAlpha { success in
        alphaSuccess = success
    }

    doBeta { success in
        betaSuccess = success
    }

    // We need to wait here
    completion(alphaSuccess && betaSuccess)
}

doAlpha and doBeta should run at the same time and, once they've both completed, the completion block should be called with the result of alpha and beta.

I've read into dispatch groups and barriers but I'm not sure which is the most appropriate, how they both introduce new scope (with regards to the two variables I'm using) and how I should implement that.

Many thanks.

Ahmad F
  • 30,560
  • 17
  • 97
  • 143
Josh Paradroid
  • 1,172
  • 18
  • 45
  • 1
    You could use `dispatch_group_t`: http://stackoverflow.com/questions/11909629/waiting-until-two-async-blocks-are-executed-before-starting-another-block – Larme Jan 23 '17 at 14:48
  • I was concerned that the completion block would be called before doAlpha and doBeta's completion blocks were called, but I can see that the second answer on that thread, using dispatch_group_enter(group) and dispatch_group_leave(group), is what I want. Thanks. – Josh Paradroid Jan 23 '17 at 14:53

2 Answers2

7

Grand Central Dispatch (GCD) is a pretty good choice of what are you trying to do here, you can achieve this by using DispatchQueue and DispatchGroup, as follows:

Swift 3:

func doEverything(completion: @escaping () -> ()) {
    let queue = DispatchQueue(label: "reverseDomain", attributes: .concurrent, target: .main)
    let group = DispatchGroup()

    group.enter()
    queue.async (group: group) {
        print("do alpha")

        group.leave()
    }

    group.enter()
    queue.async (group: group) {
            print("do beta")

        group.leave()
    }

    group.notify(queue: DispatchQueue.main) {
        completion()
    }
}

Or, you can implement it this way (which I find more readable):

func doEverything(completion: @escaping () -> ()) {
    let queue = DispatchQueue(label: "reverseDomain", attributes: .concurrent, target: .main)
    let group = DispatchGroup()

    queue.async (group: group) {
        print("do alpha")
    }

    queue.async (group: group) {
        print("do beta")
    }

    group.notify(queue: DispatchQueue.main) {
        completion()
    }
}

Note that I removed the success flag from the completion closure.

At this case, "do beta" (the execution of the second queue.async) won't be executed until "do alpha" (the execution of the first queue.async) finished, and that's because queue target is .main. If you want to let both of queue.async work concurrently, there is no need to create an extra queue, the same queue should does the work, by replacing:

let queue = DispatchQueue(label: "reverseDomain", attributes: .concurrent, target: .main)

with:

let queue = DispatchQueue(label: "reverseDomain", attributes: .concurrent)

Now, the system will control over how both of queue.async tasks should work concurrently (and obviously, group.notify will be executed after the tow of the tasks finish).

Hope this helped.

Ahmad F
  • 30,560
  • 17
  • 97
  • 143
  • I think that's what I was looking for! I'm just wondering if I need to create a queue, as my calls to doAlpha and doBeta are already asynchronous. Many thanks. – Josh Paradroid Jan 23 '17 at 15:11
  • @JoshParadroid please check the update of the answer, I mentioned the solution of what are you trying to achieve. – Ahmad F Jan 23 '17 at 16:19
0

Ahmad F's answer is correct but, as my functions with callbacks return immediately (like most do) and the callbacks are executed later, I don't need to create a new queue. Here's the original code with changes to make it work.

func doEverything(completion: @escaping (success) -> ())) {
    var alphaSuccess = false
    var betaSuccess = false

    let group = DispatchGroup()

    group.enter()
    doAlpha { success in
        alphaSuccess = success
        group.leave()
    }

    group.enter()
    doBeta { success in
        betaSuccess = success
        group.leave()
    }

    group.notify(queue: DispatchQueue.main) {
        completion(alphaSuccess && betaSuccess)
    }
}

I didn't really want to force the completion call onto the main thread, but hey ¯\_(ツ)_/¯

Josh Paradroid
  • 1,172
  • 18
  • 45