First of all, sorry for the too long question.
I know that there are few questions here that discuss similar issues but none of these talks about NSFetchedResultsController with delegate together with update in separate thread. And none of the solutions has helped me.
These are the existing questions:
- NSFetchedResultsController: using of NSManagedObjectContext during update brings to crash
- Determining which core Data attribute/property change triggered a NSFetchedResultsController update
- Core Data executeFetchRequest throws NSGenericException (Collection was mutated while being enumerated)
- Collection was mutated while being enumerated. How to determine which set?
- etc.
Now about my problem:
- I have a separate thread that updates the core data objects from the web (using a socket).
- There are few view controllers that display the data from the same core data object (each tab contains a view controller that displays its filtered data).
- Each view controller has its own instance of
NSFetchedResultsController
and the delegate is set to self.
Sometimes I receive was mutated while being enumerated
exception on updating the data in the separate thread and sometimes it crashes the app.
I have done many code manipulations in order to try to fix it and seems that nothing helps.
I have tried not to use the managed object directly from table view datasource methods. Instead of that I have created an array which holds a list of dictionaries. i fill those dictionaries in the didChangeObject
method from above. This way I don't touch the managed objects at all in the view controller.
And then I have understood that the problem is in NSFetchedResultsController that, probably, iterates the data all the time. And this is the object that conflicts with my data update in the separate thread.
The question is how can I update the core data objects in the separate thread once I have a NSFetchedResultsController with delegate (meaning that it "watches" the data and updates the delagate all the time).
NSFetchedResultsControllerDelegate implementation:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
if ( self.tabBarController.selectedIndex == 0 ) {
UITableView *tableView = self.tableView;
@try {
switch(type)
{
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView reloadSections:[NSIndexSet indexSetWithIndex:newIndexPath.section] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
@catch (NSException * e) {
NSLog(@"Exception in didChangeObject: %@", e);
}
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
if ( self.tabBarController.selectedIndex == 0 ) {
@try {
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
@catch (NSException * e) {
NSLog(@"Exception in didChangeSection: %@", e);
}
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView endUpdates];
}
In the table view datasource methods I work directly with the managed object.