在同一个线程上运行块

时间:2014-02-02 01:34:16

标签: ios objective-c multithreading asynchronous objective-c-blocks

我遇到了一个我无法弄清楚的问题。如果我有一个带有带回调的Block参数的方法签名,并且在我的方法中我使用具有另一个Block的API,则API执行async并继续我的代码并调用我的回调方法。这让我的视图控制器知道一切都已完成,而实际上通常不是由于API块。

由于我的方法总是调用回调方法(并且运行异步),我可以强制使用块运行synchronously的API调用吗?

查看控制器

[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?我尝试使用KVO时遇到的问题是,在sync过程中使用的API(Evernote)中的块数会有所不同,具体取决于发生的变化。因此,很难确定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方法来显示正在使用的其他异步内容。在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.");
}

在阅读了Rob的回答之后,看起来我需要对源进行一些重新设计,这对我来说并不是一个大问题。对于在其中运行异步代码的每个方法,方法签名将需要包含回调块。异步代码将在完成后调用该回调块。

1 个答案:

答案 0 :(得分:1)

如果您在sync方法完成之前看到了阻止传递给processSync的块,那么这表明processSync本身必须执行某些异步操作。 (您已经使用该代码更新了您的问题,这似乎就是这种情况。)如果这是事实,那么您希望(a)更改processSync方法以获取完成块参数本身,以及(b)让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以获取自己的完成处理程序。

最重要的是,当(a)最终异步进程成功完成时,您只想调用完成块;或(b)失败。但是在异步过程完成之前不要调用完成块,必要时将其嵌套,如上所示。