Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wait for an async methods to finish in a for loop

I have a for loop containing three asynchronous methods, and I want to make some treatment after this 3 async methods are finished.

 -(void)getAllUsersInformations{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
           for(User *user in users){
              [self getUserInfo:user];
           }
      //Here, I want to reload the table view for example, after finishing the for loop (executing the whole three methods).
    });
}

-(void)getUserInfo:(User*)user{
     [self getInformations:user];
     [self getExperiences:user];
     [self getEducation:user];
}

Do you have any technic to have this result? Thank you very much.

like image 916
androniennn Avatar asked Oct 13 '14 15:10

androniennn


1 Answers

One GCD approach is to use dispatch_group. So, before you start an asynchronous task, call dispatch_group_enter, and then when the asynchronous task finishes, call dispatch_group_leave, and you can then create a dispatch_group_notify which will be called when the asynchronous tasks finish. You can marry this with a completion block pattern (which is a good idea for asynchronous methods, anyway):

  1. If getInformations, getExperiences and getEducation are, themselves, all asynchronous methods, the first thing you need is some mechanism to know when they're done. A common solution is to implement a completion block pattern for each. For example:

    // added completionHandler parameter which will be called when the retrieval
    // of the "informations" is done.
    
    - (void)getInformations:(User*)user completionHandler:(void (^)(void))completionHandler {
        // do whatever you were before, but in the asynchronous task's completion block, call this
        // completionHandler()
        //
        // for example
    
        NSURLRequest *request;
    
        [NSURLConnection sendAsynchronousRequest:request queue:nil completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
            // handle the request here
    
            // the important thing is that the completion handler should
            // be called _inside_ the this block
    
            if (completionHandler) {
                completionHandler();
            }
        }];
    }
    

    Repeat this process for getExperiences and getEducation, too.

  2. Then, you can use a dispatch group to notify you of when each of these three requests are done done, calling a completion block in getUserInfo when that takes place:

    // added completion handler that will be called only when `getInformations`,
    // `getExperiences` and `getEducation` are all done.
    //
    // this takes advantage of the completion block we added to those three
    // methods above
    
    - (void)getUserInfo:(User*)user completionHandler:(void (^)(void))completionHandler {
        dispatch_group_t group = dispatch_group_create();
    
        // start the three requests
    
        dispatch_group_enter(group);
        [self getInformations:user completionHandler:^{
            dispatch_group_leave(group);
        }];
    
        dispatch_group_enter(group);
        [self getExperiences:user completionHandler:^{
            dispatch_group_leave(group);
        }];
    
        dispatch_group_enter(group);
        [self getEducation:user completionHandler:^{
            dispatch_group_leave(group);
        }];
    
        // this block will be called asynchronously only when the above three are done
    
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            if (completionHandler) {
                completionHandler();
            }
        });
    }
    
  3. And you then repeat this process at the getAllUsersInformations:

    // call new getUserInfo, using dispatch group to keep track of whether
    // all the requests are done
    
    -(void)getAllUsersInformations {
    
        dispatch_group_t group = dispatch_group_create();
    
        for(User *user in users){
            dispatch_group_enter(group);
    
            [self getUserInfo:user completionHandler:^{
                dispatch_group_leave(group);
            }];
        }
    
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            [self.tableView reloadData];
        });
    }
    

Two final thoughts:

  1. Having outlined all of that, I must confess that I would probably wrap these requests in concurrent/asynchronous custom NSOperation subclasses instead of using dispatch groups. See the "Configuring Operations for Concurrent Execution" section of the Concurrency Programming Guide. This is a more radical refactoring of the code, so I won't tackle that here, but it lets you constrain the number of these requests that will run concurrently, mitigating potential timeout issues.

  2. I don't know how many of these user requests are going on, but you might want to consider updating the UI as user information comes in, rather than waiting for everything to finish. This is, again, a more radical refactoring of the code, but might lead to something that feels more responsive.

like image 79
Rob Avatar answered Sep 20 '22 00:09

Rob