Ожидание завершения нескольких блоков

у меня есть эти методы для получения некоторой информации об объекте из интернета:

- (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 затем следует "операция завершения", которая зависит от каждой из предыдущих операций.

это не сработает. После операции считается завершенным еще до возвращения соответствующих блоков success/failure.

Я также попытался с помощью dispatch_group. Но мне не ясно, правильно ли я это делаю. К сожалению, это не работает.

5 ответов


Вы были почти там, проблема, скорее всего, в том, что эти методы асинхронны, поэтому вам нужен дополнительный шаг синхронизации. Просто попробуйте со следующим исправлением:

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

взяты из комментариев в других ответах здесь, и сообщение в блоге использование групп отправки для ожидания нескольких веб-служб, Я пришел к следующему ответу.

это решение использует dispatch_group_enter и dispatch_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 Фрагмент:

Я нахожу, что использую группы отправки достаточно, чтобы я добавлен следующий код в качестве фрагмента Xcode для легкой вставки в мой код.

теперь я типа 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),^{

});

еще одно решение-использовать обещание, который доступен в нескольких сторонних библиотек. Я автор RXPromise, который реализует обещания/а+ спецификация.

но есть, по крайней мере, две другие реализации Objective-C.

обещание представляет собой конечный результат асинхронного метода или операции:

-(Promise*) doSomethingAsync;

обещание является полной заменой для обработчик завершения. Дополнительно, должный к своим ясной спецификации и underlaying конструкции, оно имеет некоторые очень полезные характеристики которые делают его специально легким отрегулировать довольно сложные асинхронные проблемы.

что вам нужно сделать во-первых, это обернуть асинхронные методы с обработчиками завершения в асинхронные методы возвращение обещания: (Целенаправленно ваши методы возвращают конечный результат и потенциальная ошибка в более удобный обработчик завершения)

например:

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

здесь исходный асинхронный метод становится "решателем" обещания. Обещание может быть либо выполнено (успех), либо отклонено (неудача) с указанием конечного результата задачи или причины неудачи. Затем обещание будет содержать конечный результат асинхронной операции или метода.

обратите внимание, что оболочка является асинхронным методом, который возвращает немедленно обещание в" ожидающем " состоянии.

наконец, вы получаете конечный результат путем "регистрации" успеха и обработчика сбоев с помощью then метод или свойство. Несколько обещающих библиотек вокруг немного отличаются, но в основном это может выглядеть следующим образом:

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

спецификация Promise/A+ имеет минималистичный API. И вышесказанное в основном все, что нужно для реализации Promise / A+ spec - и часто достаточно во многих простых вариант использования.

однако иногда вам нужно немного больше - например, проблема OPs, которая требует "подождать" на наборе асинхронных методов, а затем сделать что-то, когда все будут завершены.

к счастью, обещание является идеальным базовым строительным блоком для создания более сложных вспомогательных методов довольно легко.

многие библиотеки обещаний предоставляют методы утилиты. Так, например, метод all (или аналогичный), который является асинхронным методом, возвращающим Обещание и принятие множества обещаний в качестве входных данных. Возвращенное обещание будет разрешено, когда все операции будут завершены или когда один из них завершится неудачно. Это может выглядеть следующим образом:

сначала постройте массив обещаний и одновременно запустите все асинхронные задачи параллельно:

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) окончательное обещание будет разрешено массивом, который содержит результат для каждой соответствующей асинхронной задачи.

в случае 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, и если у вас нет обработчиков завершения, которые вам нужно учитывать при определении зависимостей - в противном случае он становится загроможденным и разработанным.

другое решение, не упомянутое до сих пор, является Реактивный Какао. IMHO, это потрясающая библиотека, которая позволяет решать асинхронные проблемы практически любой сложности. Тем не менее, он имеет довольно крутое обучение кривой, и может добавить много кода в ваше приложение. И я думаю, 90% асинхронных проблем, о которые вы спотыкаетесь, можно решить с помощью отменяемых обещаний. Если у вас есть еще более сложные проблемы, так что взгляните на RAC.


Если вы хотите создать решение на основе блока, вы можете сделать что-то вроде

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

это быстро и грязно, и вам может потребоваться сделать блок-копии. Если несколько методов терпят неудачу, вы получите только один общий сбой.


вот мое решение без какой-либо 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();
        }];
    }
}