Search code examples
iosobjective-cgkturnbasedmatch

How to wait until Game Center match data has downloaded before showing UI


I am creating a turn based game using Game Center and GKTurnBasedMatch.

To display the list of games in a table view in my root controller, I'm calling the following method:

-(void)reloadTableView
{    
    self.matchesTable.hidden = YES;
    [_activityIndicator startAnimating];

    [GKTurnBasedMatch loadMatchesWithCompletionHandler: ^(NSArray *matches, NSError *error)
     {
         if (error)
         {
             NSLog(@"loadMatchesWithCompletionHandler error:- %@", error.localizedDescription);

         } else
         {
             for (GKTurnBasedMatch *m in matches)
             {
                 // Download match data for each match
                 [m loadMatchDataWithCompletionHandler:^(NSData *matchData, NSError *error)
                  {
                      if (error)
                      {
                          NSLog(@"loadMatchDataWithCompletionHandler:- %@", error);

                      } else
                      {
                          [self processMatches]; // extracts match data to use in UI
                      }

                  }];

                 // Snipped - Code here to add each match to the relevant array ('My turn', 'Their turn' or 'Game over') for display in the table
             }

             [self.matchesTable reloadData];

             self.matchesTable.hidden = NO;
             [_activityIndicator stopAnimating];
        }
    }]; 
}

I want the order of tasks to be:

1) Hide matchesTable and start the activity indicator
2) Call loadMatchesWithCompletionHandler
3) For each match, call loadMatchDataWithCompletionHandler
4) Call [self processMatches] on each match
5) Put each match into the relevant array
6) Stop the activity indicator and show matchesTable

However, the code runs all the way through so fast that point 6 is executed before any match data has had time to download, meaning old data is displayed in my table.

So my big question is: how do I make the program wait until every match has downloaded its match data before continuing with the rest of the method? (ie pause after point 4, and only start point 5 once match data for every match has finished downloading)


Solution

  • Because you're looping through your calls to loadMatchDataWithCompletionHandler with an additional completion block inside your existing completion block for loadMatchesWithCompletionHandler, it's firing off the calls to load match data without waiting for their response, then it calls your table reload methods, THEN your completion handlers for loading match data start firing (or at the same time as your table is reloaded, there's no real way to specify with your code).

    I would recommend using GCD to fire off your requests instead. That way, you can queue them up in dispatch_async. It would require reconfiguring the calls to get your matches/data if possible. Something like this:

    self.matchesTable.hidden = YES;
    [_activityIndicator startAnimating];
    
    dispatch_queue_t myQueue = dispatch_queue_create("My Queue",NULL);
    dispatch_async(myQueue, ^{
        NSError *error;
        NSArray *matches = [GKTurnBasedMatch loadMatches:&error];
    
        if(error)
            NSLog(@"Failed");
        else
        {
            for(GKTurnBasedMatch *m in matches)
            {
                // This will be on BG thread, but will ensure all calls are made before reloading table
                NSData *matchData = [m loadMatchData:&error];
    
                if(error)
                    NSLog(@"Failed");
                else
                    [self processMatches];
    
                // Snipped - Code here to add each match to the relevant array ('My turn', 'Their turn' or 'Game over') for display in the table
            }
        }
    
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.matchesTable reloadData];
    
            self.matchesTable.hidden = NO;
            [_activityIndicator stopAnimating];
    
        });
    });
    

    EDIT:
    Based off your comments, this structure isn't possible. I suppose something else you could do would be to know the number of calls that need to be made, and increment something every time a call finishes. If you've hit your max, then do the updates.

    ...
    int count = 0;
    for (GKTurnBasedMatch *m in matches)
    {
        // Download match data for each match
        [m loadMatchDataWithCompletionHandler:^(NSData *matchData, NSError *error)
         {
             count++;
             if (error)
             {
                 NSLog(@"loadMatchDataWithCompletionHandler:- %@", error);
             } else
             {
                 [self processMatches]; // extracts match data to use in UI
             }
    
             if(count == matches.count)
             {
                 [self.matchesTable reloadData];
    
                 self.matchesTable.hidden = NO;
                 [_activityIndicator stopAnimating];
             }
         }];
    
        // Snipped - Code here to add each match to the relevant array ('My turn', 'Their turn' or 'Game over') for display in the table
    }