NSFetchedResultsController 并不总是为 NSFetchedResultsChangeMove 调用 didChangeObject:atIndexPath:forChange

Posted

技术标签:

【中文标题】NSFetchedResultsController 并不总是为 NSFetchedResultsChangeMove 调用 didChangeObject:atIndexPath:forChangeType:newIndexPath:【英文标题】:NSFetchedResultsController doesn't always call didChangeObject:atIndexPath:forChangeType:newIndexPath: for NSFetchedResultsChangeMove 【发布时间】:2014-08-31 03:08:05 【问题描述】:

我在请求中使用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]);
        
    ];

【问题讨论】:

是什么导致了移动?从您的描述中,听起来变化正在表格视图中发生(即用户正在移动一行) 用户操作未导致其移动。更改发生在服务器上并流式传输到客户端并在 Core Data 中更新。这部分工作正常,在表格视图上调用 reloadData 会导致显示正确的顺序。 Core Data 更改是在[self managedObjectContext] 描述的上下文中发生的,是父上下文还是子上下文? @quellish 你是对的。这似乎与我的更改发生在后台上下文中并通过NSManagedObjectContextDidSaveNotification 传播到主上下文这一事实有关。 你能用处理合并通知的方法更新你的问题吗?这可能是问题所在,或者您在哪里设置通知观察 【参考方案1】:

事实证明,这个问题是由使用NSManagedObjectContextDidSaveNotification 从另一个上下文传播对managedObjectContext 的更改引起的。这篇博文详细解释了为什么这会导致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]);
        
    ];

【讨论】:

【参考方案2】:

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

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

其次,如果你需要擦除FRC并创建一个新的,你需要在table view上调用reloadData。我通常通过一些 ivars 更改 FRC 创建的逻辑来做到这一点(按照 Apple 模板,FRC 是惰性创建的),只需将 FRC 设置为nil 并调用[self.tableView reloadData];

【讨论】:

他发布的代码是他创建获取结果控制器的方式。如果您阅读他的问题,很明显他正在使用该获取的结果控制器进行获取,并希望看到委托回调(但不是,至少对于移动而言)。获取结果控制器使用performFetch: 填充其获取的对象,然后侦听影响匹配其获取请求的对象的上下文更改 - 这就是触发委托回调的原因。 @quellish 我明白你想说什么。这就是我在回答中给出建议的原因。 FRC 的谓词没有改变。只有底层数据的排序字段的值在变化。 这相当于一个新的谓词,不是吗? 没有。谓词是相同的。

以上是关于NSFetchedResultsController 并不总是为 NSFetchedResultsChangeMove 调用 didChangeObject:atIndexPath:forChange的主要内容,如果未能解决你的问题,请参考以下文章

在 Core Data 应用程序中调用 performFetch 后,是不是需要手动更新表视图?