Примите меры, когда два отдельных NSFetchRequests завершены

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

FetchRequest 1:

 [self.managedObjectContext executeFetchRequest:fetchRequest1 onSuccess:^(NSArray *results) {
        //Succcess
        [self.refreshControl endRefreshing];

    } onFailure:^(NSError *error) {
        [self.refreshControl endRefreshing];
    }];

FetchRequest 2:

 [self.managedObjectContext executeFetchRequest:fetchRequest2 onSuccess:^(NSArray *results) {
        //Succcess
        [self.refreshControl endRefreshing];

    } onFailure:^(NSError *error) {
        [self.refreshControl endRefreshing];
    }];

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

Можно ли использовать NSOperationQueue для мониторинга обоих блоков? Если нет, то какой лучший способ узнать, когда оба блока завершены?

Ответ 1

Когда у вас есть асинхронные задачи с зависимостями, у вас есть несколько вариантов:

  • Простейшим решением с наименьшими изменениями кода является использование семафора:

    // create a semaphore
    
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
    // initiate two requests (signaling when done)
    
    [self.managedObjectContext executeFetchRequest:fetchRequest1 onSuccess:^(NSArray *results) {
        [self.refreshControl endRefreshing];
        dispatch_semaphore_signal(semaphore);
    } onFailure:^(NSError *error) {
        [self.refreshControl endRefreshing];
        dispatch_semaphore_signal(semaphore);
    }];
    
    [self.managedObjectContext executeFetchRequest:fetchRequest2 onSuccess:^(NSArray *results) {
        [self.refreshControl endRefreshing];
        dispatch_semaphore_signal(semaphore);
    } onFailure:^(NSError *error) {
        [self.refreshControl endRefreshing];
        dispatch_semaphore_signal(semaphore);
    }];
    
    // now create task to to wait for these two to finish signal the semaphore
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    
        // wait for the two signals from the two fetches to be sent
    
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    
        // now do whatever you want when those two requests finish
    
        // if you need to do any UI update or do any synchronizing with the main queue, just dispatch this to the main queue
    
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"all done");
        });
    });
    

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

  • Второй подход заключается в замене ваших асинхронных запросов синхронными, которые затем можно использовать логику NSOperation standard addDependency:

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
        // completion operation
    }];
    
    NSOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        // do fetch1 _synchronously_
    }];
    
    [queue addOperation:operation1];
    
    NSOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        // do fetch2 _synchronously_
    }];
    
    [queue addOperation:operation2];
    
    [completionOperation addDependencies:@[operation1, operation2]];
    [queue addOperation:completionOperation];
    

    Этот подход требует, чтобы ваши синхронные выборки были потокобезопасными. Я не знаком с этим API, который вы используете, поэтому я не могу говорить с этим.

  • Если у вас нет синхронных исполнений ваших запросов на выборку, которые вы могли бы добавить в очередь, третий подход заключался бы в том, чтобы обернуть ваши запросы асинхронной выборки с вашим собственным параллельным подклассом NSOperation, который не будет сигнализировать isFinished до тех пор, пока не будет выполнена асинхронная операция (а также предположительно вызовите свои собственные блоки onSuccess и onFailure). После этого вы можете использовать функциональность setDependency (как показано в предыдущей точке), чтобы сделать вашу третью операцию зависимой от двух других финишей. Для получения дополнительной информации см. Раздел Настройка операций для параллельного выполнения в руководстве по программированию concurrency.

Мне хотелось бы дать более окончательный ответ, но я недостаточно разбираюсь в параметрах/ограничениях, связанных с вашей параллельной управляемой контекстной библиотекой.