Запуск блоков в одном потоке


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

Поскольку мой метод всегда вызывает метод обратного вызова (и выполняется асинхронно), могу ли я принудительно вызов API, который использует блок для запуска synchronously?

Контроллер Вида

[self.tagRepo sync:^(BOOL finished) {
    if (finished) {
        NSLog(@"Sync completed.");
        if (self.tagRepo.isSyncing)
            NSLog(@"Syncing continues...");
    }
}];

[Tagrepo Sync]

- (void)sync:(void(^)(BOOL finished))completed {
    if (self.isSyncing)
        return;

    NSLog(@"Synchronizing tags...");
    self.isSyncing = YES;

    [[EvernoteNoteStore noteStore] getSyncStateWithSuccess:^(EDAMSyncState *syncState) {
        BOOL performCallback = YES;

        if (syncState.fullSyncBefore > lastSyncTime) {
            [self processSync:syncState];
        } else if (syncState.updateCount > self.lastUpdateCount) {
            [self processSync:syncState];
        } else {
            performCallback = NO; // Block calling the call back when we begin the listTagsWithSuccess async.
            self.clientTags = [[self fetchAll] mutableCopy];
            [[EvernoteNoteStore noteStore] listTagsWithSuccess:^(NSArray *tags) {
                self.serverTags = [tags mutableCopy];
                [self processClientTags]; // Only need to make sure client sends any new tags to the server.

                // invoke call back.
                self.isSyncing = NO;
                if (completed)
                    completed(YES);
            } failure:^(NSError *error) {
                self.isSyncing = NO;
                NSLog(@"Failed to list tags.");
            }];
        }

        self.isSyncing = NO;
        if (completed && performCallback)
            completed(YES);
    } failure:^(NSError *error) {
        self.isSyncing = NO;
        NSLog(@"Failed to process a sync.");

        if (completed)
            completed(NO);
    }];
}

Что происходит, когда я вызываю метод [sync], мой NSLog показывает, что метод обратного вызова вызывается до завершения любого из моих методов processSync. Это я предполагаю, потому что вызовы метода processSync происходят в другом блоке, поэтому блок завершения в текущем потоке идет вперед и вызывается.

Использую ли я блоки в неправильной усадьбе или есть типичный способ обработки обратного вызова, которые имеют вложенные блоки внутри него. Должен ли я пытаться запустить вторичные блоки в текущем потоке через некоторые диспетчеры GCD? Создать кво? Проблема, с которой я столкнулся при попытке KVO, заключалась в том, что количество блоков в API (Evernote), используемых в процессе sync, варьируется в зависимости от того, какие изменения произошли. Поэтому трудно определить, когда происходят сообщения NSNotificationCenter, на какой стадии находится синхронизация и если это так завершено / необходимо выполнить обратный вызов или отправить другое уведомление. Я предполагаю, что есть стандартный подход, чтобы обойти это. Любые советы будут оценены!

Джонатан.

Обновление 1

Следующий код вызывается, когда я вызываю метод ' [[EvernoteNoteStore noteStore] ^listTagsWithSuccess].

- (void)getSyncStateWithSuccess:(void(^)(EDAMSyncState *syncState))success 
                        failure:(void(^)(NSError *error))failure
{
    [self invokeAsyncIdBlock:^id {
        return [[self currentNoteStore] getSyncState:[self authenticationToken]];
    } success:success failure:failure];
}

- (void)listTagsWithSuccess:(void(^)(NSArray *tags))success
                    failure:(void(^)(NSError *error))failure
{
     [self invokeAsyncIdBlock:^id {
        return [[self currentNoteStore] listTags:[self authenticationToken]];
    } success:success failure:failure];
}

- (void)invokeAsyncIdBlock:(id(^)())block
                   success:(void(^)(id))success
                   failure:(void(^)(NSError *error))failure
{
    dispatch_async(self.session.queue, ^(void) {
        id retVal = nil;
        @try {
            if (block) {
                retVal = block();
                dispatch_async(dispatch_get_main_queue(),
                               ^{
                                   if (success) {
                                       success(retVal);
                                   }
                               });
            }
        }
        @catch (NSException *exception) {
            NSError *error = [self errorFromNSException:exception];
            [self processError:failure withError:error];
        }
    });
}

Обновление 2

Я предоставляю метод processSync, чтобы показать, какие другие асинхронные вещи используются. В рамках процесса синхронизации метод я делаю еще один вызов метода Evernote SDK, а затем он заканчивается вызовом processTags. Я опускаю processServerTags, потому что код большой, но включает processClientTags, что по существу совпадает с тем, что засорено на всем протяжении processServerTags. Так что, по сути, у меня есть 3-4 вложенных асинхронных блока Evernote SDK.

- (void)processSync:(EDAMSyncState *)syncState {
    BOOL fullSync = NO;
    // If we have never updated, perform full sync.
    if (!self.lastUpdateCount)
        fullSync = YES;

    [[EvernoteNoteStore noteStore] getSyncChunkAfterUSN:self.currentChunkUSN maxEntries:200 fullSyncOnly:NO success:^(EDAMSyncChunk *syncChunk) {
        // Loop, re-grabbing the next chunk
        if (syncChunk.chunkHighUSN < syncChunk.updateCount) {
            // Cache the current sync chunk. Since only so much is handed to us
            // during this hand-shake, we cache and go get more.
            [self cacheSyncChunk:syncChunk];
            self.currentChunkUSN = syncChunk.chunkHighUSN;

            // Fetch more sync chunks.
            [self processSync:syncState];
        } else {
            // Retrieved the last sync chunk, cache it and begin processing.
            [self cacheSyncChunk:syncChunk];
            self.currentChunkUSN = syncChunk.chunkHighUSN;

            // Build list of server tags
            [self processTags];

            // Time stamp ourselves so we know when we last updated.
            self.lastSyncTime = [NSDate endateFromEDAMTimestamp:syncState.currentTime];
            self.lastUpdateCount = syncState.updateCount;
        }
    } failure:^(NSError *error) {
        NSLog(@"Failed to process full sync.");
    }];
}

- (void)processTags {

    // Process the tags found on the server first. We bring down any new tags from the server and cache them.
    // Handles any naming conflicts or duplicate conflicts that come up.
    self.clientTags = [[self fetchAll] mutableCopy];
    [self processServerTags];

    // Process client tags. We check if the client has tags that do not exist on the server and send them.
    [self processClientTags];

    // Delete any expunged tags that we still have cached.
    [self expungeTags];

    NSLog(@"Completed syncing tags.");
}

- (void)processClientTags {
    NSLog(@"Processing client tags - Ensuring server is current with client tags.");
    // Now we compare our local cache to the server, in the event new tags were created.
    // TODO: Test this.
    for (Tag *clientTag in self.clientTags) {
        // Predicate for comparing all client tags against server tags.
        // We compare GUID's and Names. Since we can't have duplicate's of either, we check.
        // It is possible that the client created a Tag (GUID #1) and created it on the server externally (GUID #2) but share the same name.
        // In this case, we have to rename them.
        NSPredicate *compareGuidPredicate = [NSPredicate predicateWithFormat:@"guid == %@", clientTag.guid];

        //Check if this note exists already on the server.
        if (![[self.serverTags filteredArrayUsingPredicate:compareGuidPredicate] count]) {
            // If not, we make sure it was never expunged.
            if ([self.expungedTags containsObject:clientTag.guid])
                continue;

            EDAMTag *serverTag = [[EDAMTag alloc] initWithGuid:nil name:clientTag.name parentGuid:nil updateSequenceNum:0];
            serverTag = [self convertManagedTag:clientTag toEvernoteTag:serverTag convertingOnlyChangedProperties:NO];

            // Check which is newer. If the server is newer, update the client, if the client is newer
            // do nothing. It will be handled later under the processClientTags method.
            [[EvernoteNoteStore noteStore] createTag:serverTag success:^(EDAMTag *tag) {
                NSLog(@"Created new %@ tag on the server.", serverTag.name);
                clientTag.guid = tag.guid;
                NSLog(@"Server GUID %@ assigned to Client GUID %@", tag.guid, clientTag.guid);
                [self saveAllChanges];
            } failure:^(NSError *error) {
                NSLog(@"Failed to create the %@ tag.n%@", clientTag.name, [error description]);
            }];
        }
    }
    NSLog(@"Client tag processing completed.");
}

После прочтения ответа Роба, похоже, мне нужно будет сделать некоторые изменения в архитектуре источника, что не является для меня большой проблемой. Для каждый метод, в котором выполняется асинхронный код, сигнатура метода должна включать блок обратного вызова. Асинхронный код вызовет этот блок обратного вызова, когда он будет выполнен.

1 4

1 ответ:

Если вы видите, что ваш блок, который вы передали sync, вызывается до того, как методы processSync будут выполнены, то это означает, что processSync сам должен выполнять какую-то асинхронную операцию. (Вы обновили свой вопрос с помощью этого кода, и, похоже, это так.) Если это действительно так, то вам нужно (а) изменить метод processSync, чтобы взять сам параметр блока завершения, и (б) заставить метод sync переместить вызов в свой собственный completed() блок, который вы передаете в processSync. Именно такой образ, вы гарантируете, что completed() не вызывается, пока он не будет действительно завершен.

Таким образом, это может выглядеть примерно так:

- (void)sync:(void(^)(BOOL finished))completed {
    if (self.isSyncing)
        return;

    NSLog(@"Synchronizing tags...");
    self.isSyncing = YES;

    [[EvernoteNoteStore noteStore] getSyncStateWithSuccess:^(EDAMSyncState *syncState) {
        if (syncState.fullSyncBefore > lastSyncTime || syncState.updateCount > self.lastUpdateCount) {
            [self processSync:syncState completionHandler:^(BOOL finished){
                self.isSyncing = NO;
                if (completed)
                    completed(finished);
            }];
        } else {
            self.clientTags = [[self fetchAll] mutableCopy];
            [[EvernoteNoteStore noteStore] listTagsWithSuccess:^(NSArray *tags) {
                self.serverTags = [tags mutableCopy];
                [self processClientTags]; // Only need to make sure client sends any new tags to the server.

                // invoke call back.
                self.isSyncing = NO;
                if (completed)
                    completed(YES);
            } failure:^(NSError *error) {
                self.isSyncing = NO;
                NSLog(@"Failed to list tags.");
                if (completed)
                    completed(NO);
            }];
        }

        // self.isSyncing = NO;
        // if (completed && performCallback)
        //     completed(YES);
    } failure:^(NSError *error) {
        self.isSyncing = NO;
        NSLog(@"Failed to process a sync.");

        if (completed)
            completed(NO);
    }];
}
Обратите внимание, что это устраняет булеву функцию performCallback, поскольку мы просто гарантируем, что все пути вызывают обратный вызов, и в случае processSync вызов обратного вызова откладывается до тех пор, пока processSync не завершит свой асинхронный процесс Первым. Это, очевидно, предполагает, что вы будете рефакторировать processSync, чтобы взять собственный обработчик завершения.

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