问题
I'm using NSFetchedResultsController with sortDescriptors on the request to populate a table with a lot of results in it. I notice that when a change occurs that moves a row from near the bottom of the table to the top, didChangeObject:atIndexPath:forChangeType:newIndexPath: is not called at all.
Strangely, I can work around this by iterating through all the fetched objects and accessing any attribute on them right after calling performFetch.
Any tips on what the problem might be, or is this just an obscure Apple bug?
Here is my code:
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;
}
Updated September 12, 2014:
I'm saving all data in a background managed object context, and it seems to be related to the issues I'm seeing. Here is my code for merging changes from to the main object context:
+(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]);
}
}];
}
回答1:
It turns out that this problem was caused by the fact that the changes to managedObjectContext were being propagated from another context using NSManagedObjectContextDidSaveNotification. This blog post explains in detail why this causes a problem for NSFetchedResultsController, and how to fix it:
http://www.mlsite.net/blog/?p=518
Here is the specific fix in the context of my code above:
+(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:
You are creating a brand new fetched results controller. So there was no change (like insert, delete, update), so the delegate is not called. There are two ways to deal with this.
First, you could use the existing FRC and just change the predicate of its fetch request (the fetch request itself is readonly). Then you just call performFetch. Depending on your needs this could be sufficient.
Second, if you need to wipe the FRC and create a new one, you need to call reloadData on the table view. I usually do this by changing the logic of FRC creation via some ivars (following the Apple template, the FRC is created lazily), and just set the FRC to nil and call [self.tableView reloadData];.
来源:https://stackoverflow.com/questions/25588624/nsfetchedresultscontroller-doesnt-always-call-didchangeobjectatindexpathforch