Nested Dispatch Groups Swift

IOS Swift Nested DispatchGroup for Nested Network requests handling arrays

Could not figure out a proper long-term solution to fix the issue, so I had to hack around it and use a bad workaround which just removes duplicate users everytime a new user is grabbed and then reloads the collectionView. However, note that this will and can cause problems in the code.

How do I use DispatchGroup within nested Firebase asynchronous observeSingleEvent() methods Swift?

You should make both firstArray and nameArray:

var firstArray = [firstType]()
var nameArray = [secondType]()

And leave() the dispatchGroup inside the innermost callback. And consider wrapping it in a defer{}, protecting against any multiple paths inside the callback in the future.

func loop(inputArray: [inputType], doneLoop: @escaping (_ firstArray: [firstType],_ secondArray: [secondType]) -> Void) {

var firstArray = [firstType]()
var nameArray = [secondType]()

let dispatchGroup = DispatchGroup()

for element in inputArray {

let firstRef = Database.database().reference(withPath: "firstPath")

//Enter dispatch group
dispatchGroup.enter()
firstRef.observeSingleEvent(of: .value, with: { snapshot in

//Storing first snapshot value

let firstSnapshotValue = snapshot.value as! firstType

let secondRef = Database.database().reference(withPath: "secondPath")

secondRef.observeSingleEvent(of: .value, with: { snapshot in
//Leave dispatch group
defer { dispatchGroup.leave() }

//Storing second snapshot value
let secondSnapshotValue = snapshot.value as? String

//Append retrieved values to arrays
firstArray.append(firstSnapshotValue)
secondArray.append(secondSnapshotValue)
})

})

}

//Notify on main queue and execute completion handler
dispatchGroup.notify(queue: .main) {
doneLoop(firstArray, secondArray)
}

}

Also, if the values from secondRef are independent, consider unnesting the inner observeSingleEvent call (and adding additional enter() and leave() calls).

Concurrency consideration

As is, your code will potentially/eventually lose writes to both firstArray and secondArray. The faster Firebase's observeSingleEvent executes its with block, the sooner the data will be lost. To avoid this, all writes (appends) should be done from a serial queue:

let queue = DispatchQueue(label: "tld.company.app.queue")
queue.async() {
// Write
}
queue.sync() {
// Read
}

Or, even better, keep reads concurrent with the .barrier flag:

let queue = DispatchQueue(label: "tld.company.app.queue", attributes: .concurrent)
queue.async(flags: .barrier) {
// Write
}
queue.sync() {
// Read
}

Read more: Create thread safe array in Swift

Nested async calls in Swift

The problem you face is a common one: having multiple asynchronous tasks and wait until all are completed.

There are a few solutions. The most simple one is utilising DispatchGroup:

func loadUrls(urls: [URL], completion: @escaping ()->()) {
let grp = DispatchGroup()

urls.forEach { (url) in
grp.enter()
URLSession.shared.dataTask(with: url) { data, response, error in
// handle error
// handle response
grp.leave()
}.resume()
}

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

The function loadUrls is asynchronous and expects an array of URLs as input and a completion handler that will be called when all tasks have been completed. This will be accomplished with the DispatchGroup as demonstrated.

The key is, to ensure that grp.enter() will be called before invoking a task and grp.leave is called when the task has been completed. enter and leave shall be balanced.

grp.notify finally registers a closure which will be called on the specified dispatch queue (here: main) when the DispatchGroup grp balances out (that is, its internal counter reaches zero).

There are a few caveats with this solution, though:

  • All tasks will be started nearly at the same time and run concurrently
  • Reporting the final result of all tasks via the completion handler is not shown here. Its implementation will require proper synchronisation.

For all of these caveats there are nice solutions which should be implemented utilising suitable third party libraries. For example, you can submit the tasks to some sort of "executer" which controls how many tasks run concurrently (match like OperationQueue and async Operations).

Many of the "Promise" or "Future" libraries simplify error handling and also help you to solve such problems with just one function call.

Nested dispatch queues not firing inner dispatch queue

Without nesting you register the listen separately so every group will act according to it's submitted tasks ,while when you nest them , then the notify of the inner group will depend on the outer one plus whether or not the it's (the inner group) tasks ended/working when it's notify is added upon triggering the outer notify

    let dispatchGroupOne = DispatchGroup()

let dispatchGroupTwo = DispatchGroup()

let dispatchGroupThird = DispatchGroup()

dispatchGroupThird.enter()
dispatchGroupOne.notify(queue: .main) {
// Gets here
dispatchGroupThird.leave()
}

dispatchGroupThird.enter()
dispatchGroupTwo.notify(queue: .main) {
// Gets here
dispatchGroupThird.leave()
}

dispatchGroupThird.notify(queue: .main) {
// All groups are done
}


Related Topics



Leave a reply



Submit