I have a UITableView
that uses an NSFetchedResultsController
as data source.
The core data store is updated in multiple background threads running in parallel (each thread using it's own NSManagedObjectContext
).
The main thread observes the NSManagedObjectContextDidSaveNotification
notification and updates it's context with mergeChangesFromContextDidSaveNotification:
.
Sometimes it happens that the NSFetchedResultsController
sends an
NSFetchedResultsChangeUpdate
event with an indexPath that does not exist
anymore at that point.
For example: The result set of the fetched results controller contains 1 section with 4 objects. The first object is deleted in one thread. The last object is updated in a different thread. Then sometimes the following happens:
But the fetched results controller contains only 3 objects now, and if call
MyManagedObject *obj = [controller objectAtIndexPath:indexPath]
to update the table view cell according to the NSFetchedResultsChangeUpdate
event, this crashes with a NSRangeException
exception.
Thank you for any help or ideas!
I have now found a solution for my problem. In the case of an update event, there is no need to call
[self.controller objectAtIndexPath:indexPath]
because the updated object is already supplied as the anObject parameter to the -controller:didChangedObject:...
delegate.
I have therefore replaced -configureCell:atIndexPath:
by a -configureCell:withObject:
method that uses the updated object directly. This seems to work without problems.
The code looks now like this:
- (void)configureCell:(UITableViewCell *)cell withObject:(MyManagedObject *)myObj
{
cell.textLabel.text = myObj.name;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyCellIdentifier"];
[self configureCell:cell withObject:[self.controller objectAtIndexPath:indexPath]];
return cell;
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
{
UITableView *tableView = self.tableView;
switch(type) {
/* ... */
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] withObject:anObject];
break;
/* ... */
}
}