I'm baffled as to what is the best way to achieve this. I am trying to keep a running total of Double values that I am looping through and adding together, via a network call. Everything I've read says to use DispatchGroup. My completion either calls too early or doesn't get called at all and I've tried every configuration of .enter, .leave, and .wait that I can think of.
let group = DispatchGroup()
var runningTotal: Double = 0.00
ref.observeSingleEvent(of: .value) { (snapshot) in
guard let bills = snapshot.value as? [String: AnyObject] else {
//error
return
}
for billId in bills.keys {
group.enter()
print("Entering")
Database.database().reference().child("bills").child(billId).observeSingleEvent(of: .value, with: { (snapshot) in
guard let bill = snapshot.value as? [String: AnyObject] else {
return
}
if let amount = bill["amount"] as? Double {
runningTotal += amount
}
group.leave()
print("Leaving")
})
}
completion(runningTotal)
}
group.wait()
}
A couple of thoughts:
Avoid ever calling wait from the main thread. The use cases for that are pretty limited. The notify is a much safer way to achieve the same thing.
Make sure you call leave from every path inside you’re loop. This can be achieved nicely with defer block.
So:
func foo(completion: @escaping (Double?) -> Void) {
ref.observeSingleEvent(of: .value) { snapshot in
guard let bills = snapshot.value as? [String: AnyObject] else {
//error
completion(nil)
return
}
let group = DispatchGroup()
var runningTotal = 0.0
for billId in bills.keys {
group.enter()
print("Entering")
Database.database().reference().child("bills").child(billId).observeSingleEvent(of: .value) { snapshot in
defer { group.leave() }
guard let bill = snapshot.value as? [String: AnyObject] else {
return
}
if let amount = bill["amount"] as? Double {
runningTotal += amount
}
print("Leaving")
}
}
group.notify(queue: .main) {
completion(runningTotal)
}
}
}
You should wait until all group tasks are done, then call completion block.
Like below.
var runningTotal: Double = 0.00
ref.observeSingleEvent(of: .value) { (snapshot) in
guard let bills = snapshot.value as? [String: AnyObject] else {
//error
return
}
let group = DispatchGroup()
for billId in bills.keys {
group.enter()
print("Entering")
Database.database().reference().child("bills").child(billId).observeSingleEvent(of: .value, with: { (snapshot) in
guard let bill = snapshot.value as? [String: AnyObject] else {
group.leave()
return
}
if let amount = bill["amount"] as? Double {
runningTotal += amount
}
group.leave()
print("Leaving")
})
}
group.wait()
completion(runningTotal)
}