4

I spend the whole night debugging a simple app. The app retrieves one image (yes one..intend to make my life easier) from web, and displays it in table view. I do that as a practice to learn Core Data. Before I fix it, the error message shows below:

2012-09-30 06:16:12.854 Thumbnail[34862:707] CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of sections. The number of sections contained in the table view after the update (1) must be equal to the number of sections contained in the table view before the update (0), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted). with userInfo (null)

Basically it's saying that something goes wrong with FRC delegate methods. At one hand, section number changes from 0 to 1. At the other hand, "0 inserted, 0 deleted". So how the section number can increase? That should not happen.. Hence the error.

I fix the bug by simply adding [self.tableView reloadData] to my FRC setup method. I got the inspiration from this post, yet I don't quite understand it. The answer seems like too complicated and project specific. Could someone explain why adding reloadData can fix the bug? The answer might be an easy one, I hope so.

Key components of my app, if that matters:

  • Use UIManagedDocument to establish the core data stack
  • Create a helper method to download images from Flickr API
  • In NSManagedObject subclass file, try fetch image from persistent store. If it's not there yet, insert it into MOC.

    - (void)setupFetchedResultsController
    {
        NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
        NSEntityDescription *entity = [NSEntityDescription entityForName:@"BigImage" inManagedObjectContext:self.document.managedObjectContext];
        [fetchRequest setEntity:entity];
    
        NSSortDescriptor *imageDescriptor = [[NSSortDescriptor alloc] initWithKey:@"image" ascending:YES];
        NSArray *sortDescriptors = [NSArray arrayWithObject: imageDescriptor];
        [fetchRequest setSortDescriptors:sortDescriptors];
    
        [fetchRequest setFetchBatchSize:20];
    
        // Create fetch results controller
        self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.document.managedObjectContext sectionNameKeyPath:nil cacheName:@"Root"];
    
        self.fetchedResultsController.delegate = self;
    
        NSError *error;
        if (![self.fetchedResultsController performFetch:&error])
        {
            NSLog(@"Error in performFetch: %@, %@", error, [error userInfo]);
        }
    
        // Critical!! I add this line to fix the bug!
            [self.tableView reloadData];
        }
    
Community
  • 1
  • 1
Philip007
  • 3,190
  • 7
  • 46
  • 71

1 Answers1

7

A fetched results controller tracks only changes to the managed object contents after the first fetch. These changes are then propagated to the table view using the delegate methods didChangeSection, didChangeObject etc.

But there is no automatic mechanism that the result of the initial fetch is sent to the table view. That is the reason why you have to call reloadData after performFetch.

However, there is a situation where this seems to work automatically. UITableViewController calls reloadData in viewWillAppear (if the table is loaded the first time). Therefore, if you set up the FRC for example in viewDidLoad, reloadData will be called in viewWillAppear, and you don't have to call it manually.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382