Reputation: 4138
The situation is I need to loop through multiple objects and make a network call on each.
After all of the network calls are finished, I will call my completion block with all of the data that I collected from these network calls.
To accomplish this, I am attempting to use a dispatch group, entering when the network call begins and leaving when it is finished:
for user in users {
dispatch_group_enter(downloadGroup)
UserManager.retrieveFriendsForUser(user, completed: { (usersFriends, fault) in
guard let usersFriends = usersFriends else {
return
}
dispatch_group_leave(downloadGroup)
})
}
I am then waiting for them to finish with the following:
dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER);
However, it does not seem to wait for all of my grouped network calls to finish.
Here is all of the code in complete context; I am looping through many users and then querying for their friends. And I want the method to return all of their friends combined (by concatenating all the results to the allNonFriends array):
class func retrieveNonFriendsOfFriends(completed : (users : [BackendlessUser]?, fault : Fault?) -> Void) {
var allNonFriends = [BackendlessUser]()
let downloadGroup = dispatch_group_create() // 2
dispatch_group_enter(downloadGroup) // 3
UserManager.retrieveCurrentUsersFriends { (users, fault) in
guard let users = users else {
print("Server reported an error: \(fault)")
completed(users: nil, fault: fault)
return
}
for user in users {
dispatch_group_enter(downloadGroup) // 3
UserManager.retrieveFriendsForUser(user, completed: { (usersFriends, fault) in
guard let usersFriends = usersFriends else {
print("Server reported an error: \(fault)")
return
}
let nonFriends = usersFriends.arrayWithoutFriends(users)
allNonFriends += nonFriends
dispatch_group_leave(downloadGroup) // 4
})
}
}
dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER);
completed(users: allNonFriends, fault: nil)
}
Upvotes: 0
Views: 165
Reputation: 17586
For each time you call dispatch_group_enter()
, you need to call dispatch_group_leave()
; if you call dispatch_group_enter()
5 times then you also need to call dispatch_group_leave()
5 times.
You make 1 call for retrieving the current user's friends
dispatch_group_enter(downloadGroup) // 3
UserManager.retrieveCurrentUsersFriends
and then #users calls once that has completed
for user in users {
dispatch_group_enter(downloadGroup) // 3
You don't balance that first call for retrieving the current user's friends.
if userFriends
is nil you never end up calling dispatch_group_leave()
for that user.
The end result is that you never complete the dispatch_group
and you wait forever.
dispatch_group_enter(downloadGroup) // 3
UserManager.retrieveCurrentUsersFriends { (users, fault) in
defer {
// Whether we return early or use the users we want to leave the group
// Balances the initial enter()
dispatch_group_leave(downloadGroup)
}
... process users
for user in users {
dispatch_group_enter(downloadGroup) // 3
UserManager.retrieveFriendsForUser(user, completed: { (usersFriends, fault) in
// No matter the outcome of the call we want to leave the
// dispatch_group so we don't wait forever
defer {
// Balances the enter() for each user
dispatch_group_leave(downloadGroup)
}
.... process userFriends
}
}
Recommended reading: http://commandshift.co.uk/blog/2014/03/19/using-dispatch-groups-to-wait-for-multiple-web-services/
Upvotes: 2