等待多个块完成

我有这些方法来从互联网上检索一些对象信息:

- (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; 

下载的东西存储在对象的属性,所以这就是为什么成功函数没有返回。

现在,我想有一个像这样的方法:

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

除了调用上面的所有方法外,没有别的办法,只有在每个方法执行完成或失败后才返回。

我怎样才能做到这一点?

提示:我知道级联方法调用在每个其他成功块将工作。 但是,当后面的实现包含更多的方法时,这既不“干净”,也没有帮助。

编辑:

我尝试在NSOperation运行每个调用,并将这些NSOperations添加到NSOperationQueue然后依次执行“完成操作”,这取决于上述每个操作。

这是行不通的。 由于这些操作甚至在它们各自的成功/失败块返回之前被认为已经完成。

我也知道,我可以把每一个这样的电话都放在一个分类的NSOperation中,我可以自己决定何时开始完成。 但是,这听起来像是一种矫枉过正,还是唯一的出路呢?

这就是我所做的:

 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]; 

编辑:

现在我尝试了dispatch_group东西。 但对我来说不是我正在做的正确的方式:

 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(); }); 

不幸的是,这不是这样工作的。 “成功”总是在最后显示。

你几乎在那里,问题很可能是这些方法是asynchronous的,所以你需要一个额外的同步步骤。 试试以下修复:

 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(); }); } 

从这里的其他答案的评论和博客文章使用调度组来等待多个Web服务 ,我得到了以下答案。

此解决scheme使用dispatch_group_enterdispatch_group_leave来确定每个中间任务何时完成。 当所有任务完成后,调用最终的dispatch_group_notify块。 然后你可以调用你的完成块,知道所有的中间任务已经完成。

 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(); } }); 

另一个解决scheme是使用一个在几个第三方库中可用的Promise 。 我是RXPromise的作者,它实现了Promises / A +规范 。

但是至less有两个Objective-C实现。

Promise表示asynchronous方法或操作的最终结果:

 -(Promise*) doSomethingAsync; 

承诺是完成处理程序的完整替代品。 另外,由于其清晰的规范和底层devise,它具有一些非常有用的特性,使得处理相当复杂的asynchronous问题变得特别容易。

你需要做的第一件事就是把你的asynchronous方法用完成处理程序包装成asynchronous方法返回一个Promise :(有意思的是,你的方法返回最终的结果和一个更方便的完成处理程序中的潜在错误

例如:

 - (RXPromise*) downloadAppInfo { RXPromise* promise = [RXPromise new]; [self downloadAppInfoWithCompletion:^(id result, NSError *error) { if (error) { [promise rejectWithReason:error]; } else { [promise fulfillWithValue:result]; } }]; return promise; } 

在这里,原始的asynchronous方法成为诺言的“解决者”。 承诺可以通过指定任务的最终结果或失败的原因来实现(成功)或拒绝(失败)。 承诺将保留asynchronous操作或方法的最终结果。

请注意,包装器是一个asynchronous方法,它立即返回一个“等待”状态的承诺。

最后,通过使用方法或属性“注册”成功和失败处理程序来获得最终结果 。 less数承诺图书馆有所不同,但基本上可能看起来如下:

 `promise.then( <success-handler>, <error-handler> )` 

Promise / A + Specification有一个简约的API。 以上基本上都是实现Promise / A +规范的一个必要条件,而且在很多简单的用例中通常是足够的。

然而,有时候你需要更多的东西 – 比如说OPs问题,它需要在一组asynchronous方法上“等待”,然后在所有的asynchronous方法完成后再做一些事情。

幸运的是,Promise是构build更复杂的帮助器方法的理想基础。

许多Promise库提供实用方法。 所以例如一个方法all (或类似的),这是一个asynchronous方法返回一个承诺,并承诺数组作为input。 所有操作完成或失败时,退还的承诺将被解决。 它可能如下所示:

首先构造一个promise数组,同时并行启动所有asynchronous任务:

 NSArray* tasks = @[ [self downloadAppInfo], [self getAvailableHosts], [self getAvailableServices], [self getAvailableActions], ]; 

注意:在这里,任务已经在运行(并可能完成)!

现在,使用一个辅助方法,它完全符合上面的内容:

 RXPromise* finalPromise = [RXPromise all:tasks]; 

获得最终结果:

 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 }); 

请注意,当返回的promise将以某种方式在all:方法中parsing时, 成功失败处理程序将被调用。

返回的promise( finalPromise )将在什么时候解决

  1. 所有任务成功,或成功
  2. 一个任务失败

对于情况1),最终的承诺将用包含每个对应的asynchronous任务的结果的数组来解决。

在情况2)最后的承诺将解决失败的asynchronous任务的错误。

(注意:这里有几个可用的库可能有所不同)

RXPromise库有一些额外的function:

在承诺的非循环图中转发消除信号的复杂消除。

一种指定处理程序将运行的分派队列的方法。 例如,队列可用于同步对共享资源的访问

 self.usersPromise = [self fetchUsers]; self.usersPromise.thenOn(dispatch_get_main_queue(), ^id(id users) { self.users = users; [self.tableView reloadData]; }, nil); 

与其他方法相比, dispatch_group解决scheme遭受阻塞线程的事实。 这不是很“asynchronous”。 如果不是不可能实现取消也是相当复杂的。

NSOperation解决scheme似乎是一个混合的祝福。 如果你已经拥有NSOperations ,并且如果在定义依赖关系时你没有需要考虑的完成处理程序,那么它可能是优雅的,否则就会变得混乱和精细。

还没有提到的另一个解决scheme是Reactive Cocoa 。 恕我直言,这是一个了不起的图书馆,可以让你解决几乎任何复杂的asynchronous问题。 但是,它有一个相当陡峭的学习曲线,并可能会添加很多代码到您的应用程序。 我猜,90%的asynchronous问题可以用可取消的承诺来解决。 如果你有更复杂的问题,那么看看RAC。

如果你想创build一个基于块的解决scheme,你可以做类似的事情

 - (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]; } 

这是一种快速和肮脏,你可能需要做块副本。 如果有多个方法失败,则只会得到一个整体失败。

这是我的解决scheme没有任何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(); }]; } }