H. A. Samad H. A. Samad - 3 months ago 36
iOS Question

Waiting for multiple blocks to finish

I have those methods to retrieve some object information from the internet:

- (void)downloadAppInfo:(void(^)())success
failure:(void(^)(NSError *error))failure;
- (void)getAvailableHosts:(void(^)())success
failure:(void(^)(NSError *error))failure;
- (void)getAvailableServices:(void(^)())success
failure:(void(^)(NSError *error))failure;
- (void)getAvailableActions:(void(^)())success
failure:(void(^)(NSError *error))failure;


The downloaded stuff gets stored in object properties, so that is why the success functions return nothing.

Now, I want to have one method like this:

- (void)syncEverything:(void(^)())success
failure:(void(^)(NSError *error))failure;


Which does nothing else than calling all the methods above, and returning only after every single method has performed its success or failure block.

How can I do this?

Hint: I am aware that cascading the methods calls in each others success block would work. But this is either 'clean' nor helpful when later implementations include further methods.

EDIT:

I tried running each of the calls in an
NSOperation
and adding those
NSOperations
to an
NSOperationQueue
followed by a "completion operation" which depends on every one of the preceding operations.

This won't work. Since the operations are considered completed even before their respective success/failure blocks return.

I am also aware, that I could put each of those calls in a subclassed NSOperation, where I can myself decide when to fire the completion block. But this sounds like an overkill, or is it the only way to go?

This is what I did:

NSOperation *completionOperation = [NSOperation new];
completionOperation.completionBlock = sucess; // Defined somewhere else

NSOperationQueue *queue = [NSOperationQueue new];

for(ANAppliance *appliance in _mutAppliances)
{
NSLog(@"ADD app");

NSOperation *operation = [NSOperation new];

operation.completionBlock =
^{
[self downloadHostsForApplianceWithName:appliance.networkSettings.name
success:^
{
NSLog(@"DOWNLOADED");
//sucess();
}
failure:^(NSError *error)
{
//failure(error);
}];
};


[completionOperation addDependency:operation];

[queue addOperation:operation];
}

[queue addOperation:completionOperation];


EDIT:

Now I tried the
dispatch_group
thing. But it is not to me wether I am doing it the right way:

for(Appliance *appliance in _mutAppliances)
dispatch_group_async(group,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^ {
NSLog(@"Block START");

[appliance downloadAppInfo:^
{
NSLog(@"Block SUCCESS");
}
failure:^(NSError *error)
{
NSLog(@"Block END");
}];

NSLog(@"Block END");
});

dispatch_group_notify(group,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^ {
NSLog(@"FINAL block");
sucess();
});


Unfortunately, it is not working this way. "Block SUCCESS" shows always at the end.

Answer

You were almost there, the problem is most likely to be that those methods are asynchronous, so you need an extra synchronization step. Just try with the following fix:

for(Appliance *appliance in _mutAppliances) {
  dispatch_group_async(
     group,
     dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       dispatch_semaphore_t sem = dispatch_semaphore_create( 0 );

       NSLog(@"Block START");

       [appliance downloadAppInfo:^{
          NSLog(@"Block SUCCESS");
            dispatch_semaphore_signal(sem);
       }
       failure:^(NSError *error){
         NSLog(@"Block FAILURE");
         dispatch_semaphore_signal(sem);
       }];

       dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

       NSLog(@"Block END");
 });

 dispatch_group_notify(
   group,
   dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{
     NSLog(@"FINAL block");
     success();
 });
}