等待多个街区完成

时间:2013-09-24 14:54:08

标签: ios objective-c-blocks grand-central-dispatch nsoperation nsoperationqueue

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

- (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,然后执行“完成操作”,这取决于前面的每个操作。< / p>

这不起作用。由于即使在各自的成功/失败块返回之前,操作也被认为已完成。

我也尝试过使用dispatch_group。但我不清楚我是以正确的方式做到这一点。不幸的是,它没有用。

5 个答案:

答案 0 :(得分:14)

从这里的其他答案的评论和博客文章Using dispatch groups to wait for multiple web services中得出,我得出了以下答案。

此解决方案使用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();
    }
});

Grand Central Dispatch - 派遣小组

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

  

分组块允许聚合同步。您的应用程序可以提交多个块并跟踪它们是否全部完成,即使它们可能在不同的队列上运行。在完成所有指定任务之前无法取得进展时,此行为会很有用。

Xcode片段:

我发现自己使用的调度组足以让我added the following code as an Xcode Snippet轻松插入我的代码。

现在我输入DISPATCH_SET并插入以下代码。然后,为每个异步块复制并粘贴enter / leave

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),^{

});

答案 1 :(得分:10)

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

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

答案 2 :(得分:2)

另一个解决方案是使用 Promise ,它可以在少数第三方库中使用。 我是RXPromise的作者,它实现了Promises/A+ specification

但至少有两个其他的Objective-C实现。

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

-(Promise*) doSomethingAsync;

承诺是完成处理程序的完全替代。此外,由于其清晰的规范和底层设计,它具有一些非常有用的功能,使得处理相当复杂的异步问题变得特别容易。

首先需要做的是将带有完成处理程序的异步方法包装到异步方法中返回Promise : (有目的地,您的方法在更方便的完成处理程序中返回最终结果潜在错误

例如:

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

这里,原始异步方法成为promise的“解析器”。通过指定任务的最终结果或失败的原因,可以履行(成功)或拒绝(失败)承诺。然后,promise将保留异步操作或方法的最终结果。

请注意,包装器是一个异步方法,它会立即返回处于“挂起”状态的promise。

最后,通过使用then方法或属性“注册”成功和失败处理程序来获取最终结果。周围的少数承诺库略有不同,但基本上它可能如下所示:

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

Promise / A +规范具有简约API。以上基本上是实现Promise / A +规范所需要的 - 并且在许多简单的用例中通常就足够了。

然而,有时你需要更多 - 例如OPs问题,它需要在一组异步方法上“等待”,然后在完成所有方法后做一些事情。

幸运的是,Promise是一个理想的基本构建模块,可以很容易地构建更复杂的辅助方法。

许多Promise库提供实用程序方法。例如,一个方法all(或类似的),它是一个异步方法,返回一个Promise并将一个promises数组作为输入。返回的承诺将在所有操作完成或失败后解决。它可能如下所示:

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

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

请注意,在all:方法中以某种方式解析返回的promise时,将调用 success failure 处理程序。

时,将解决返回的承诺( finalPromise
  1. 所有任务成功完成,或
  2. 一项任务失败
  3. 对于案例1),最终的承诺将通过一个数组来解决,该数组包含每个相应异步任务的结果。

    在案例2)中,最终的承诺将通过失败的异步任务的错误来解决。

    (注意:这里可用的库很少)

    RXPromise库有一些额外的功能:

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

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

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

    与其他方法相比,dispatch_group解决方案会遇到阻塞线程的问题。这不是“异步”。如果不是不可能实现取消,它也是非常复杂的。

    NSOperation解决方案似乎是一种混合的祝福。只有当你已经拥有NSOperations时才会优雅,如果你没有在定义依赖关系时需要考虑的完成处理程序 - 否则,它会变得杂乱无章。

    目前尚未提及的另一种解决方案是Reactive Cocoa。恕我直言,它是一个很棒的库,可以让你解决几乎任何复杂性的异步问题。但是,它有一个非常陡峭的学习曲线,可能会为您的应用添加大量代码。我想,你偶然发现的90%的异步问题都可以通过可取消的承诺来解决。如果您遇到更复杂的问题,那么请看一下RAC。

答案 3 :(得分:1)

如果您想创建基于块的解决方案,您可以执行类似

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

它有点快速和肮脏,你可能需要阻止复制。如果多个方法失败,则只会出现一次失败。

答案 4 :(得分:0)

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