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.
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();
});
}
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.
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),^{
});
One other solution is to use a Promise which is available in a few third party libraries. I'm the author of RXPromise, which implements the Promises/A+ specification.
But there are at least two other Objective-C implementations.
A Promise represents the eventual result of an asynchronous method or operation:
-(Promise*) doSomethingAsync;
The promise is a complete replacement for the completion handler. Additionally, due to its clear specification and underlaying design, it has some very useful features which make it especially easy to handle rather complex asynchronous problems.
What you need to do first, is to wrap your asynchronous methods with completion handlers into asynchronous methods returning a Promise: (Purposefully, your methods return the eventual result and a potential error in a more convenient completion handler)
For example:
- (RXPromise*) downloadAppInfo {
RXPromise* promise = [RXPromise new];
[self downloadAppInfoWithCompletion:^(id result, NSError *error) {
if (error) {
[promise rejectWithReason:error];
}
else {
[promise fulfillWithValue:result];
}
}];
return promise;
}
Here, the original asynchronous method becomes the "resolver" of the promise. A promise can be either fulfilled (success) or rejected (failure) with either specifying the eventual result of the task or the reason of the failure. The promise will then hold the eventual result of the asynchronous operation or method.
Note that the wrapper is an asynchronous method, which returns immediately a promise in a "pending" state.
Finally, you obtain the eventual result by "registering" a success and a failure handler with a then
method or property. The few promise libraries around do differ slightly, but basically it may look as follows:
`promise.then( <success-handler>, <error-handler> )`
The Promise/A+ Specification has a minimalistic API. And the above is basically ALL one need for implementing the Promise/A+ spec - and is often sufficient in many simple use cases.
However, sometimes you need bit more - for example the OPs problem, which require to "wait" on a set of asynchronous methods and then do something when all have completed.
Fortunately, the Promise is an ideal basic building block to construct more sophisticated helper methods quite easily.
Many Promise libraries provide utility methods. So for example a method all
(or similar) which is an asynchronous method returning a Promise and taking an array of promises as input. The returned promise will be resolved when all operations have been completed, or when one fails. It may look as follows:
First construct an array of promises, and simultaneously starting all asynchronous tasks in parallel:
NSArray* tasks = @[
[self downloadAppInfo],
[self getAvailableHosts],
[self getAvailableServices],
[self getAvailableActions],
];
Note: here, the tasks are already running (and may complete)!
Now, use a helper method which does exactly what stated above:
RXPromise* finalPromise = [RXPromise all:tasks];
Obtain the final results:
finalPromise.then(^id( results){
[self doSomethingWithAppInfo:results[0]
availableHosts:results[1]
availableServices:results[2]
availableActions:results[3]];
return nil;
}, ^id(NSError* error) {
NSLog(@"Error %@", error); // some async task failed - log the error
});
Note that either the success or the failure handler will be called when the returned promise will be resolved somehow in the all:
method.
The returned promise (finalPromise) will be resolved, when
- all tasks succeeded successfully, or when
- one task failed
For case 1) the final promise will be resolved with an array which contains the result for each corresponding asynchronous task.
In case 2) the final promise will be resolved with the error of the failing asynchronous task.
(Note: the few available libraries may differ here)
The RXPromise library has some additional features:
Sophisticated cancellation which forwards a cancellation signal in the acyclic graph of promises.
A way to specify a dispatch queue where the handler will run. The queue can be used to synchronize access to shared resources for example, e.g.
self.usersPromise = [self fetchUsers];
self.usersPromise.thenOn(dispatch_get_main_queue(), ^id(id users) {
self.users = users;
[self.tableView reloadData];
}, nil);
When compared to other approaches, the dispatch_group
solution suffers from the fact that it blocks a thread. This is not quite "asynchronous". It's also quite complex if not impossible to implement cancellation.
The NSOperation
solution appears to be a mixed blessing. It may be elegant only if you already have NSOperations
, and if you have no completion handlers which you need to take into account when defining the dependencies - otherwise, it becomes cluttered and elaborated.
Another solution, not mentioned so far, is Reactive Cocoa. IMHO, it's an awesome library which lets you solve asynchronous problems of virtually any complexity. However, it has a quite steep learning curve, and may add a lot of code to your app. And I guess, 90% of asynchronous problems you stumble over can be solved with cancelable promises. If you have even more complex problems, so take a look at RAC.
If you want to create a block based solution you could do something like
- (void)syncEverything:(void(^)())success failure:(void(^)(NSError *error))failure
{
__block int numBlocks = 4;
__block BOOL alreadyFailed = NO;
void (^subSuccess)(void) = ^(){
numBlocks-=1;
if ( numBlocks==0 ) {
success();
}
};
void (^subFailure)(NSError*) = ^(NSError* error){
if ( !alreadyFailed ) {
alreadyFailed = YES;
failure(error);
}
};
[self downloadAppInfo:subSuccess failure:subFailure];
[self getAvailableHosts:subSuccess failure:subFailure];
[self getAvailableServices:subSuccess failure:subFailure];
[self getAvailableActions:subSuccess failure:subFailure];
}
It's kind of quick and dirty, and you might need to do block copys. If more than one method fails, you will only get one overall failure.
Here is my solution without any dispatch_group.
+(void)doStuffWithCompletion:(void (^)(void))completion{
__block NSInteger stuffRemaining = 3;
void (^dataCompletionBlock)(void) = ^void(void) {
stuffRemaining--;
if (!stuffRemaining) {
completion();
}
};
for (NSInteger i = stuffRemaining-1; i > 0; i--) {
[self doOtherStuffWithParams:nil completion:^() {
dataCompletionBlock();
}];
}
}
来源:https://stackoverflow.com/questions/18985015/waiting-for-multiple-blocks-to-finish