NSFetchedResultsController returns incorrect NSFetchedResultsChangeType
I am creating NSFetchedResultsController
with the following Fetch request inviewDidLoad
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:NSStringFromClass([Person class])];
fetchRequest.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:NSStringFromSelector(@selector(sortDate))
ascending:NO]];
NSFetchedResultsController *controller = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.mainManagedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
controller.delegate = delegate;
NSError *error;
if (![controller performFetch:&error]) {
NSLogError(error);
}
// Connecting up all the delegates for UITableView
And reloading it in viewWillAppear
with[self.tableView reloadData]
Simplified Person
@interface Person : NSManagedObject
/// an NSTimeInterval representing the Unix Epoch
@property (nonatomic, retain) NSNumber *sortDate;
- (void)viewed;
@end
@implementation Person
// nothing fancy or custom - no extra KVO or primitive assignments
@dynamic sortDate;
- (void)viewed {
self.sortDate = @([NSDate date].timeIntervalSince1970);
}
@end
My data is generated with a network request to fetch the json heap and convert it to NSManagedObject
s. The sort looks ok.
1 Person A time 1437498898
2 Person B time 1437498897
3 Person C time 1437498896
When I set the property sortDate
to Person C
with a method -viewed
, in <NSFetchedResultsControllerDelegate>
I get the change type NSFetchedResultsChangeMove
and the list is sorted like this:
1 Person C time 1437498900
2 Person A time 1437498898
3 Person B time 1437498897
When I try to do the same for Person B
, I instead get the change type NSFetchedResultsChangeUpdate
and the list is sorted like this:
1 Person C time 1437498900
2 Person A time 1437498898
3 Person B time 1437498905
I can constantly update sortDate
for faces A
and C
, and they will continue to sort correctly. Any changes to are Person B
not reflected until the application restarts and then Person B
stops updating. Uninstalling and reinstalling my app (clearing CoreData) doesn't help.
If I put the command print
in - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
, then I see that it has Person B
sortDate
indeed updated to the new value, which should force NSFRC to create an event Move
instead of Update
changing the type.
// Original Sort (logs from cellForRowAtIndexPath)
Time 1437506506 - Person B
Time 1437506490 - Person C
Time 1437506381 - Person D
Time 1437506128 - Person A
// Change Person A
Updating SortDate 1437506128 --> 1437506599
Time 1437506599 - Person A for NSFetchedResultsChangeMove
// Sorted Correctly
Time 1437506599 - Person A
Time 1437506506 - Person B
Time 1437506490 - Person C
Time 1437506381 - Person D
// Change Person B
Updating SortDate 1437506506 --> 1437506610
Time 1437506610 - Person B for NSFetchedResultsChangeUpdate
// Sorted Incorrectly
Time 1437506599 - Person A
Time 1437506610 - Person B
Time 1437506490 - Person C
Time 1437506381 - Person D
So why is the NSFetchedResultsController
return wrong NSFetchedResultsChangeType
?
source to share
From the documentation NSFetchedResultsControllerDelegate
:
A change is reported when the changed attribute of an object is one of the sort descriptors used in the select request.
In this case, the object is supposed to be updated, but no delegate is sent to a separate update message.
An update is reported when the state of the objects changes, but the changed attributes are not part of the sort keys.
Hopefully this makes it clear why you are getting different types of changes in your callback. With this in mind, I would suggest clearing the selected result controller and delegating the callbacks:
- Eliminate redundant predicate in FRC
- Use the sort key is the name of the attribute:
"sortDate"
. - Make sure the delegate callback method moves the table view cell to the new pointer path i.e. by calling
deleteRowsAtIndexPaths
andinsertRowsAtIndexPaths
as needed.
Final thought: The erroneous behavior you describe for one particular object (while others seem to work as expected) indicates other possible errors, for example in relation to attribute updates sortDate
.
Also, you didn't explain the variable keypath
in the selected result controller. The grouping may be contributing to the behavior that you observe.
Finally, it is possible that you are a victim of the rare target dispatcher delegate bug described here (including the workaround). Also, don't forget that if you don't need to animate the changes, you can always just invalidate the FRC and call reloadData
.
If all else fails, I would refactor with NSDate
instead NSNumber
, which is more intuitive anyway.
source to share