XmasRights
XmasRights

Reputation: 1509

Dealing with multiple completion handlers

I'm trying to coordinate several completion handlers for each element in an array.

The code is essentially this:

var results = [String:Int]()

func requestData(for identifiers: [String])
{
    identifiers.forEach
    {   identifier in

        service.request(identifier, completion: { (result) in
            result[identifier] = result
        })
    }

    // Execute after all the completion handlers finish
    print(result)
}

So each element in the Array is sent through a service with a completion handler, and all the results are stored in an array. Once all of these handlers complete, I wish to execute some code.

I attempted to do this with DispatchQueue

var results = [String:Int]()

func requestData(for identifiers: [String])
{
    let queue = DispatchQueue.init(label: "queue")

    identifiers.forEach
    {   identifier in

        service.request(identifier, completion: { (result) in
            queue.sync
            {
                result[identifier] = result
            }
        })
    }

    // Execute after all the completion handlers finish
    queue.sync
    {
        print(result)
    }
}

but the print call is still being executed first, with an empty Dictionary

Upvotes: 12

Views: 7797

Answers (2)

xmhafiz
xmhafiz

Reputation: 3538

First, take note that your service.request(...) is processed in asynchronous mode. Another problem is you want to finish all the service request in that loop.

My suggestion is create the function with completion handler and add a counter on each loop done. Your function will be similarly as below.

var results = [String:Int]()

func requestData(for identifiers: [String], callback:@escaping (Bool) -> Void)
{
    var counter = 0
    var maxItem = identifiers.count

    identifiers.forEach
    {   identifier in

        service.request(identifier, completion: { (result) in
            result[identifier] = result
            counter += 1
            if counter == maxItem {
                callback(true) // update completion handler to say all loops request are done
            }
            // if not, continue the other request
        })
    }
}

This is how another part of your code will call the function and wait for callback

requestData(for identifiers:yourArrays) { (complete) in

    if complete {
        print(results)
    }
}

Don't forget to manage if errors happened.

Upvotes: 0

Doug Mead
Doug Mead

Reputation: 942

If I understand what are you are trying to do correctly, you probably want to use a DispatchGroup

Here is an example:

let group = DispatchGroup()

var letters = ["a", "b", "c"]

for letter in letters {
    group.enter()
    Server.doSomething(completion: { [weak self] (result) in
        print("Letter is: \(letter)")
        group.leave()
    })
}

group.notify(queue: .main) {
    print("- done")
}

This will print something like:

b
c
a
// ^ in some order
- done

Upvotes: 41

Related Questions