vikzilla
vikzilla

Reputation: 4138

Dispatch queue not waiting

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

Answers (1)

Kevin
Kevin

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

Problem 1

You don't balance that first call for retrieving the current user's friends.

Problem 2

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.

Solution

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

Related Questions