15

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

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

除了调用上述所有方法,并且仅在每个方法执行其成功或失败块之后才返回,它什么也不做。

我怎样才能做到这一点?

提示:我知道在其他成功块中级联方法调用是可行的。但是,当以后的实现包含更多方法时,这既不“干净”也不有用。

尝试:

我尝试在 an 中运行每个调用NSOperation并将它们添加NSOperations到 anNSOperationQueue后跟一个“完成操作”,这取决于前面的每个操作。

这行不通。因为即使在它们各自的成功/失败块返回之前,这些操作也被认为已完成。

我也尝试使用dispatch_group. 但我不清楚我是否以正确的方式做这件事。不幸的是,它不起作用。

4

5 回答 5

17

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

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

大中央调度 - 调度组

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

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

Xcode 片段:

我发现自己使用了足够多的 Dispatch Groups,因此我将以下代码添加为 Xcode Snippet,以便轻松插入到我的代码中。

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

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

});

迅速:

let dispatchGroup = DispatchGroup()

dispatchGroup.enter()

dispatchGroup.leave()

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

}
于 2016-04-13T19:54:25.810 回答
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();
 });
}
于 2013-09-24T16:15:51.627 回答
2

另一种解决方案是使用一些第三方库中提供的Promise 。我是RXPromise的作者,它实现了Promises/A+ 规范

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

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

-(Promise*) doSomethingAsync;

Promise 完全替代了完成处理程序。此外,由于其明确的规范和底层设计,它具有一些非常有用的特性,使其特别容易处理相当复杂的异步问题。

您首先需要做的是将带有完成处理程序的异步方法包装到返回 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 将保存异步操作或方法的最终结果。

请注意,包装器是一个异步方法,它立即返回处于“待处理”状态的承诺。

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

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

Promise/A+ 规范有一个简约的 API。以上内容基本上是实现 Promise/A+ 规范的全部需要 - 在许多简单的用例中通常就足够了。

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

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

许多 Promise 库都提供了实用方法。因此,例如一个方法all(或类似方法),它是一个返回 Promise 并将一组 Promise 作为输入的异步方法。当所有操作完成或失败时,返回的 Promise 将被解决。它可能如下所示:

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

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:处理程序。

返回的承诺 ( finalPromise ) 将被解决,当

  1. 所有任务都成功成功,或者当
  2. 一项任务失败

对于案例 1),最终的 promise 将使用一个数组来解决,该数组包含每个相应异步任务的结果。

在情况 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% 的异步问题都可以通过可取消的 Promise 来解决。如果您有更复杂的问题,请查看 RAC。

于 2013-09-24T20:51:15.857 回答
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];
}

这有点快速和肮脏,您可能需要进行块复制。如果不止一种方法失败,你只会得到一个整体失败。

于 2013-09-24T15:55:54.993 回答
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();
        }];
    }
}
于 2016-08-31T14:56:19.443 回答