Pretty image
Marcus walks you through using the NSFetchedResultsController, a powerful new class in the Core Data API that can make your iPhone, iPod Touch, and Mac apps significantly richer.

Development for iPhone and iPod Touch is getting easier, and Apple is giving us access to more of its OS goodies. With the release of the 3.0 OS for the iPhone and iPod Touch, we gained the ability to use Core Data directly on our touch devices. With this inclusion came a new class to the Core Data API, the NSFetchedResultsController. The NSFetchedResultsController is intended to be the bridge between Core Data and the UITableView. However, that description scarcely does it justice.

In addition to just being a bridge between the two APIs (Cocoa Touch and Core Data), it also plays an active role in keeping the UI updated to what is occurring within Core Data. This syncing is powerful and easy, allowing us to update the Core Data repository asynchronously to the UI and the UI will “just work.” Getting it working is a little tricky, so I thought an article spelling it out would be helpful.

Constructing The Controller

Easily the most complicated step to using the NSFetchedResultsController is its construction. Fortunately this complexity pays off later when we are accessing it. The NSFetchedResultsController is constructed as a “wrapper” around the NSFetchRequest. The reason for this is that the data set the NSFetchedResultsController is working with is living (aka dynamic). When something changes in Core Data that impacts the results the NSFetchedResultsController is handling, it will respond to those changes. Therefore instead of just initializing with a static set of data, the NSFetchedResultsController is initialized with the information to construct that set of data. This allows the NSFetchedResultsController both to monitor the data and to “refresh” itself automatically.

Therefore, the construction of the NSFetchedResultsController starts off with the construction of a NSFetchRequest.

 NSFetchRequest *fetchRequest = nil;
 fetchRequest = [[NSFetchRequest alloc] init];
 
 NSEntityDescription *entity = nil;
 entity = [NSEntityDescription entityForName:@"FeedItem"
  inManagedObjectContext:[self managedObjectContext]];
  [fetchRequest setEntity:entity];
 
 [fetchRequest setFetchBatchSize:20];
 
 NSSortDescriptor *sortDescriptor = nil;
 sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"pubDate"
  ascending:NO];
 NSArray *sortDescriptors = nil;
 sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
 [fetchRequest setSortDescriptors:sortDescriptors];

Once the NSFetchRequest has been constructed we next need to build the wrapping NSFetchedResultsController.

 NSFetchedResultsController *frc = nil;
 frc = [[NSFetchedResultsController alloc]
  initWithFetchRequest:fetchRequest
  managedObjectContext:[self managedObjectContext]
  sectionNameKeyPath:nil
  cacheName:@"Root"];
 
 [frc setDelegate:self];
 [self setFetchedResultsController:frc];
 [frc release], frc = nil;

The NSFetchedResultsController takes the NSFetchRequest that we constructed previously along with a reference to the NSManagedObjectContext, a section key path, and a cache name.

The section key path is used to generate the section names. If, for example, we are building an Address Book application, then we may want this key path to lead to the last name of the people being displayed in the table view.

The cache name is used to cache the data retrieved by NSFetchedResultsController for faster access while the UITableView is being used. If Core Data finds an existing cache with the same name and it matches the NSFetchRequest then it will load that cache directly and avoid the overhead of computing the section and index information. Otherwise the cache is deleted and rebuilt.

Once the NSFetchedResultsController has been constructed, we want to give it a delegate. This delegate, implementing the NSFetchedResultsControllerDelegate protocol, will receive callbacks whenever the data has changed. The above code block, taken from our example application (and linked to below), passes self as the delegate because this NSFetchedResultsControllerDelegate is being constructed by the UITableViewController.

So Now What?

Congratulations! You’ve made it through the hard part. So now that we have this wonderful NSFetchedResultsController, what do we do with it? To see this, lets start with the methods that a UITableViewDatasource handles.

 - (NSInteger)tableView:(UITableView*)tableView
  numberOfRowsInSection:(NSInteger)section
 {
  id <NSFetchedResultsSectionInfo> sectionInfo = nil;
  sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
  return [sectionInfo numberOfObjects];
 }

The -tableView:numberOfRowsInSection: is one of the two truly required methods in a UITableViewDatasource. However, as you can see from this implementation, the NSFetchedResultsController has reduced this method down to 3 lines of code. In fact you could easily reduce this to a single line of code. Since the NSFetchedResultsController is aware of both the data and the structure of the table, it knows exactly how many rows are contained in each section. All we have to do is ask it.

 - (NSInteger)numberOfSectionsInTableView:(UITableView*)tableView
 {
  return [[fetchedResultsController sections] count];
 }

A less frequently used method, The -numberOfSectionsInTableView: method can also be reduced to a single line of code. Because the NSFetchedResultsController knows the number of sections, and calculates these for us automatically, it is again a single line of code to ask it.

Ordinarily the most complicated method in a standard UITableView is the -tableView:cellForRowAtIndexPath:. Fortunately the NSFetchedResultsController helps us a little bit here also.

 - (UITableViewCell*)tableView:(UITableView*)tableView
  cellForRowAtIndexPath:(NSIndexPath*)indexPath
 {
  UITableViewCell *cell = nil;
  cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier];
  if (!cell) {
  cell = [[[UITableViewCell alloc]
  initWithStyle:UITableViewCellStyleSubtitle
  reuseIdentifier:kCellIdentifier] autorelease];
  [cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
  }
 
  [self configureCell:cell atIndexPath:indexPath];
 
  return cell;
 }

While the construction of the cell remains unchanged, we can easily retrieve the correct NSManagedObject with a single line of code. However, for reasons explained below, we handle the population of the cell in another method: -configureCell:atIndexPath:.

 - (void)configureCell:(UITableViewCell*)cell
  atIndexPath:(NSIndexPath*)indexPath
 {
  NSManagedObject *mo = nil;
  NSString *temp = nil;
  mo = [fetchedResultsController objectAtIndexPath:indexPath];
  temp = [[managedObject valueForKey:@"title"] description];
  [[cell textLabel] setText:temp];
  temp = [[managedObject valueForKey:@"author"] description];
  [[cell detailTextLabel] setText:temp];
 }

Depending on how the data is constructed, determining the right dataset for the cell can be a complex operation. However with the NSFetchedResultsController it is again a single line of code to retrieve the correct NSManagedObject and then use it to populate the cell.

Where is the Magic?

While all of this is nice, and probably is enough justification on its own to use a NSFetchedResultsController, there is more. When the data has changed outside of the control of our UITableViewController, it would be nice if the controller was notified so that the table could be refreshed. This is where the true power and utility of the NSFetchedResultsController lies. With the implementation of four methods, our UITableViewController can automatically receive notifications of changes in the data and respond appropriately. The four methods correspond to a simple workflow: I am about to do something, I am doing something, I am done doing something.

I Am About to Do Something

The -controllerWillChangeContent: method is the “I am about to do something” phase of the notification. If our table structure is simple and short (i.e. one section and a dozen rows or so) then we can just call [[self tableView] reloadData] and be done with it. However, assuming our table view is more complex, we want to start a transaction against the UITableView in preparation for the “I am doing something” methods that are about to follow:

 - (void)controllerWillChangeContent:(NSFetchedResultsController*)controller
 {
  [[self tableView] beginUpdates];
 }

A call to [[self tableView] beginUpdates] tells the UITableView that we are going to be making one or more changes to its structure and for it to wait to make visual changes until we are done.

I Am Doing Something

The -controller: didChangeSection: atIndex: forChangeType: method is one of two methods in the “I am doing something” phase of our workflow. This method is called once for each section that has been added or removed. Fortunately this method is easy to handle: we simply instruct the UITableView to insert or remove the section affected.

 - (void)controller:(NSFetchedResultsController*)controller
  didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
  atIndex:(NSUInteger)sectionIndex
  forChangeType:(NSFetchedResultsChangeType)type
 {
  NSIndexSet *set = [NSIndexSet indexSetWithIndex:sectionIndex];
  switch(type) {
  case NSFetchedResultsChangeInsert:
  [[self tableView] insertSections:set
  withRowAnimation:UITableViewRowAnimationFade];
  break;
  case NSFetchedResultsChangeDelete:
  [[self tableView] deleteSections:set
  withRowAnimation:UITableViewRowAnimationFade];
  break;
  }
 }

The -controller: didChangeObject: atIndexPath: forChangeType:newIndexPath: method is a bit more complicated. This method is called once for each row that is affected. This could be an insert, delete, move, or just an update to the data. For each of those possible states we need to handle it by informing the UITableView.

 - (void)controller:(NSFetchedResultsController*)controller
  didChangeObject:(id)anObject
  atIndexPath:(NSIndexPath*)indexPath
  forChangeType:(NSFetchedResultsChangeType)type
  newIndexPath:(NSIndexPath*)newIndexPath
 {
  UITableView *tv = [self tableView];
  switch(type) {
  case NSFetchedResultsChangeInsert:
  [tv insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
  withRowAnimation:UITableViewRowAnimationFade];
  break;
  case NSFetchedResultsChangeDelete:
  [tv deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
  withRowAnimation:UITableViewRowAnimationFade];
  break;
  case NSFetchedResultsChangeUpdate:
  [self configureCell:[tv cellForRowAtIndexPath:indexPath]
  atIndexPath:indexPath];
  break;
  case NSFetchedResultsChangeMove:
  [tv deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
  withRowAnimation:UITableViewRowAnimationFade];
  [tv insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
  withRowAnimation:UITableViewRowAnimationFade];
  break;
  }
 }

I mentioned above that the population of the UITableViewCell was extracted into its own method for a reason. The -controller: didChangeObject: atIndexPath: forChangeType: newIndexPath: method is the reason. When a row is updated as opposed to inserted, deleted or moved, we want to just refresh the values displayed. Instead of duplicating the cell population code here, it is moved into its own method, which is then called from both the -tableView:cellForRowAtIndexPath: method and here.

I Am Done Doing Something

The final method, which completes the workflow, is the -controllerDidChangeContent: method. This is our “I am done doing something” call. It is here that we close up the transaction for the UITableView so that it can update the user interface. If we need to update other aspects of the user interface (perhaps a toolbar), this would be the appropriate place.

 - (void)controllerDidChangeContent:(NSFetchedResultsController*)controller
 {
  [[self tableView] endUpdates];
 }

See It in Action

To see a fully working example of the NSFetchedResultsController I recommend downloading the example RSS Reader that I posted on GitHub.

In this example I set up a UITableViewController as described in this article and use it to display the headlines from the PragProg RSS Feed. Apart from the use of a NSFetchedResultsController, the interesting part of this example is that the retrieval of the data is completely separate from the UITableViewController. The retrieval is performed in a NSOperation and written directly to the Core Data Repository. Because of the NSFetchedResultsController, however, the data is automatically updated in the UITableViewController as soon as the operation performs a save on the NSManagedObjectContext.

The NSFetchedResultsController is a powerful addition to our toolkit for Cocoa Touch development. It allows us to remove a tremendous amount of code from our UITableViewController objects.

And of course, less code means fewer bugs :)

Discuss this article with the author and other readers in the magazine forum. -Mike

Marcus S. Zarra is the owner of Zarra Studios LLC and the creator of seSales and iWeb Buddy as well as being a co-author of “Cocoa Is My Girlfriend",” a wildly popular blog covering all aspects of Cocoa development. Marcus has been developing software since the mid-1980s and has written software in all of the major technological fields. Marcus has been using Core Data since its original release in OS X 10.4 Tiger and has released numerous applications and papers covering all of the topics of Core Data including the book, Core Data: Apple's API for Persisting Data on Mac OS X for The Pragmatic Programmer.