Запуск блоков в одном потоке
Я столкнулся с небольшой проблемой, которую не могу понять. Если у меня есть сигнатура метода с параметром блока с обратным вызовом в нем, и в моем методе я использую 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 ответ:
Если вы видите, что ваш блок, который вы передали
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
, чтобы взять собственный обработчик завершения.Суть в том, что вы хотите вызывать блоки завершения только в том случае, если (а) конечные асинхронные процессы успешно завершены или (б) завершились неудачно. Но не вызывайте блок завершения, пока этот асинхронный процесс не будет завершен, вложив его по мере необходимости, как показано выше.