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 【问题描述】:我在请求中使用NSFetchedResultsController
和sortDescriptors
来填充包含大量结果的表。我注意到,当发生将行从表格底部附近移动到顶部的更改时,根本不会调用 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的主要内容,如果未能解决你的问题,请参考以下文章