NSFetchedResultsController并不总是调用didChangeObject:atIndexPath:forChangeType:newIndexPath:for NSFetchedResultsChangeMove

时间:2014-08-31 03:08:06

标签: ios uitableview core-data nsfetchedresultscontroller

我在请求中使用NSFetchedResultsControllersortDescriptors来填充包含大量结果的表格。我注意到当发生一个从桌子底部附近移动到顶部的行时,根本不会调用didChangeObject:atIndexPath:forChangeType:newIndexPath:

奇怪的是,我可以通过迭代所有获取的对象并在调用performFetch之后立即访问它们上的任何属性来解决这个问题。

关于问题可能是什么的提示,或者这只是一个模糊的Apple漏洞?

这是我的代码:

NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:@"MyObject" inManagedObjectContext:context];
request.sortDescriptors = @[NSSortDescriptor sortDescriptorWithKey:@"order" ascending:NO]];
request.fetchBatchSize = 20;
NSFetchedResultsController *fetched = [[NSFetchedResultsController alloc]
                                       initWithFetchRequest:request
                                       managedObjectContext:context
                                         sectionNameKeyPath:nil
                                                  cacheName:nil];
fetched.delegate = self;
NSError *error = nil;
if (![fetched performFetch:&error]) {
    NSLog(@"Unresolved error fetching objects: %@", error);
}

// Should not be necessary, but objects near the bottom won't move to the top without it.
for (MyObject *o in fetched.fetchedObjects) {
    o.someAttribute;
}

2014年9月12日更新:

我将所有数据保存在后台管理对象上下文中,这似乎与我所看到的问题有关。这是我将更改合并到主对象上下文的代码:

+(void)initSaveListener {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mergeChanges:)
                                                 name:NSManagedObjectContextDidSaveNotification
                                               object:[self privateContext]];
}

+(void)mergeChanges:(NSNotification*)notification {
    NSManagedObjectContext *context = [self mainContext];

    [context performBlock:^{
        [context mergeChangesFromContextDidSaveNotification:notification];
        NSError *error = nil;
        if (![context save:&error]) {
            NSLog(@"error merging changes %@, %@", error, [error userInfo]);
        }
    }];
}

2 个答案:

答案 0 :(得分:2)

事实证明,此问题是由managedObjectContext的更改使用NSManagedObjectContextDidSaveNotification从另一个上下文传播引起的。这篇博文详细解释了为什么这会导致NSFetchedResultsController出现问题,以及如何解决问题:

http://www.mlsite.net/blog/?p=518

以上是我的代码上下文中的具体修复:

+(void)mergeChanges:(NSNotification*)notification {
    NSManagedObjectContext *context = [self mainContext];

    // Fault all objects that have changed so that NSFetchedResultsController will see the changes.
    NSArray *objects = [[notification userInfo] objectForKey:NSUpdatedObjectsKey];
    for (NSManagedObject *object in objects) {
        [[context objectWithID:[object objectID]] willAccessValueForKey:nil];
    }

    [context performBlock:^{
        [context mergeChangesFromContextDidSaveNotification:notification];
        NSError *error = nil;
        if (![context save:&error]) {
            NSLog(@"error merging changes %@, %@", error, [error userInfo]);
        }
    }];
}

答案 1 :(得分:0)

您正在创建一个全新的抓取结果控制器。所以没有变化(如插入,删除,更新),因此不会调用委托。有两种方法可以解决这个问题。

首先,您可以使用现有的FRC,只需更改其获取请求的谓词(获取请求本身为readonly)。然后你只需拨打performFetch。根据您的需要,这可能就足够了。

其次,如果您需要擦除FRC并创建一个新FRC,则需要在表视图上调用reloadData。我通常通过一些ivars改变FRC创建的逻辑来做到这一点(遵循Apple模板,懒惰地创建FRC),只需将FRC设置为nil并调用[self.tableView reloadData];