NSFetchedResultsControllerDelegate在GCD托管环境中难以理解的行为

时间:2012-11-15 23:50:00

标签: objective-c core-data nsfetchedresultscontroller iphonecoredatarecipes

我有CoreData和相关NSFetchedResultsController的表格。 Controller具有上下文,它在主队列中创建并以只读方式工作。当然,tableviewcontroller实施NSFetchedResultsControllerDelegate协议。

看一下它实现的方法:

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
    switch (type) {
        case NSFetchedResultsChangeInsert:
            NSLog(@"Inserted in %@", [NSString stringWithUTF8String:dispatch_queue_get_label(dispatch_get_current_queue())]);
            [_tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeDelete:
            [_tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeUpdate:
            NSLog(@"Updated in %@", [NSString stringWithUTF8String:dispatch_queue_get_label(dispatch_get_current_queue())]);
            [_tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeMove:
            [_tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            [_tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}

在后台我在我的应用程序中下载数据和更新数据库。数据库的更新总是一样的。它在我的数据管理器的方法中更新:

- (void)saveDataInBackgroundInForeignContext:(void (^)(NSManagedObjectContext *))saveBlock completion:(void (^)(void))completion {
    AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    dispatch_async([delegate.dispatcher queueForDataSavingInModel:self.modelName], ^{
        [self saveDataInForeignContext:saveBlock];

        dispatch_sync(dispatch_get_main_queue(), ^{
            completion();
        });
    });
}


- (void)saveDataInForeignContext:(void (^)(NSManagedObjectContext *))saveBlock {
    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {

        NSManagedObjectContext *localContext = [[NSManagedObjectContext alloc] init];
        [localContext setPersistentStoreCoordinator:coordinator];

        [localContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
        [self.managedObjectContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
        [[NSNotificationCenter defaultCenter] addObserver:self.managedObjectContext
                                                 selector:@selector(mergeChangesFromContextDidSaveNotification:)
                                                     name:NSManagedObjectContextDidSaveNotification
                                                   object:localContext];

        saveBlock(localContext);

        if (localContext.hasChanges) {

            [self updateLastUpdateDateInConformedUpdatedObjects:localContext];

            NSError *error = nil;
            BOOL success = [localContext save:&error];
            if (!success) {
                NSLog(@"Saving in foreign context failed. %@", error.userInfo);
            }
        }

        [localContext release];
    }
}
saveBlock中的

我修改上下文取决于来自服务器的数据。 所以,在结果中我有奇怪的行为:

在第一个列表中注意NSLog方法中的controller:didChangeObject:atIndexPath。让我们来看看日志:

2012-11-16 02:59:33.376 [27824:5303] Inserted in ru.idecide.saving.calls // WTF WHY?!
2012-11-16 03:05:56.219 [27824:c07] Updated in com.apple.main-thread

ru.idecide.saving.calls - queue保存数据。

这并不重要,一切正常,但方法insertRowsAtIndexPaths在调用插入后(明显)立即更新后,在2-3秒内对UI产生影响。为什么会发生这种情况,我该怎么做才能避免呢?

1 个答案:

答案 0 :(得分:1)

问题在于:

    [[NSNotificationCenter defaultCenter] addObserver:self.managedObjectContext
                                             selector:@selector(mergeChangesFromContextDidSaveNotification:)
                                                 name:NSManagedObjectContextDidSaveNotification
                                               object:localContext];

您将主线程上下文直接链接到后台队列上下文。当您在后台线程上运行的localContext发布通知时,通知将传递给同一队列(后台队列)上的观察者(self.managedObjectContext)。

您需要将通知传送到主线程,然后再将其发送到self.managedObjectContext。给自己一个新的方法来接收后台队列上的通知并将其转发给主线程:

- (void)backgroundContextDidSave:(NSNotification *)note {
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.managedObjectContext mergeChangesFromContextDidSaveNotification:note];
    });
}

然后在注册通知时使用该方法选择器:

    [[NSNotificationCenter defaultCenter] addObserver:self.managedObjectContext
                                             selector:@selector(backgroundContextDidSave:)
                                                 name:NSManagedObjectContextDidSaveNotification
                                               object:localContext];