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 (append
s) 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
How to Prevent Eventstore Access Error on First Run
How to Call Presentviewcontroller from Inside Class
iOS 11 PDFkit Not Updating Annotation Position
Generate Avaudiopcmbuffer with Avaudiorecorder
Scenekit Physics Add Velocity in Local Space
Type '()' Cannot Conform to 'View'
Appending Text to Nstextview in Swift 3
How to Rotate an Arkit 4X4 Matrix Around Y Using Apple's Simd Library
(Appkit) Tab Insertion Inside of Nstextblock
Building an Nsoutline View with Check Marks
Bit Field Larger Than 64 Shifts in Swift
Assemble a List of Users with Geofire/Firebase
How to Simplify Swift Enum Custom Init
Realitykit - Load Another Scene from the Same Reality Composer Project
Drawing a Gradient Color in an Arc with a Rounded Edge
How to Pass One Swiftui View as a Variable to Another View Struct