Waiting for multiple blocks to finish

Hasib. A. Samad picture Hasib. A. Samad · Sep 24, 2013 · Viewed 7.7k times · Source

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 neither 'clean' nor helpful when later implementations include further methods.

Attempts:

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 also tried using dispatch_group. But it is not clear to me wether I am doing it the right way. Unfortunately, it is not working.

Answer

pkamb picture pkamb · Apr 13, 2016

Drawn from the comments in other answers here, and the blog post Using dispatch groups to wait for multiple web services, I arrived at the following answer.

This solution uses dispatch_group_enter and dispatch_group_leave to determine when each intermediate task is running. When all tasks have finished, the final dispatch_group_notify block is called. You can then call your completion block, knowing that all intermediate tasks have finished.

dispatch_group_t group = dispatch_group_create();

dispatch_group_enter(group);
[self yourBlockTaskWithCompletion:^(NSString *blockString) {

    // ...

    dispatch_group_leave(group);
}];

dispatch_group_enter(group);
[self yourBlockTaskWithCompletion:^(NSString *blockString) {

    // ...

    dispatch_group_leave(group);
}];

dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{

    // All group blocks have now completed

    if (completion) {
        completion();
    }
});

Grand Central Dispatch - Dispatch Groups

https://developer.apple.com/documentation/dispatch/dispatchgroup

Grouping blocks allows for aggregate synchronization. Your application can submit multiple blocks and track when they all complete, even though they might run on different queues. This behavior can be helpful when progress can’t be made until all of the specified tasks are complete.

Xcode Snippet:

I find myself using Dispatch Groups enough that I've added the following code as an Xcode Snippet for easy insertion into my code.

Now I type DISPATCH_SET and the following code is inserted. You then copy and paste an enter/leave for each of your async blocks.

Objective-C:

dispatch_group_t group = dispatch_group_create();

dispatch_group_enter(group);

dispatch_group_leave(group);

dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{

});

Swift:

let dispatchGroup = DispatchGroup()

dispatchGroup.enter()

dispatchGroup.leave()

dispatchGroup.notify(queue: .global()) {

}