The Cocoa Core Data documentation makes frequent reference to the fact that Managed Object Contexts (MOCs) are “scratchpads“, in which objects can be moved around willy-nilly without affecting the permanent store. This is all well and good, but it leaves important questions unanswered, such as: “How do you use this fact to implement a ‘cancel’ button?” The answer turns out to be a little more complicated than you might suppose, but can be summed up in one word: Notifications.
The Problem
Let’s say you have a small iPhone app with two view controllers:
List
, which displays a list of employee objectsEmployee
, which displays an employee’s name and salary
To keep things simple, let’s assume that the List
controller brings up a child controller whenever you select an employee name, and that it also has an “add” button, which brings up a blank
Employee
controller. Finally, let’s assume that Employee
controllers are always in “edit” mode, with “cancel” and “save” buttons in their nav bars.
Assuming that this is all built in Core Data, how would you implement the “cancel” button? Pretty easy, you might think: Just assign the Employee
controller its own MOC, and then pop the controller if the user selects “cancel”. Presto – the “scratch” MOC disappears, and no changes are committed.
What happens if the user presses “save”? Well, the “scratch” MOC will write its changes to the persistent store — and the List
MOC will be blissfully unaware of them. If, for instance, you change an employee’s name with this method, the List
controller will display the old name when you return to it. This is not so good.
NSFetchedResultsControllerDelegate
Your first thought might be to have the List
controller adopt the NSFetchedResultsControllerDelegate
protocol. This is, indeed, necessary, but it is only half the solution. The real problem is that MOCs aren’t notified when the data in their Persistent Store Controller change. Fortunately, MOCs themselves do post notifications when they save data, and other MOCs can capture these notifications, update their managed objects, and then pass messages to their delegates to update the necessary views.
NSManagedObjectContextDidSaveNotification
Here’s a snippet of code demonstrating the key concept – this could be an implementation of the List
controller’s add
method, invoked when the “add” button is pressed:
- (void)add
{
// Create "working" MOC
NSManagedObjectContext* moc = [[NSManagedObjectContext alloc] init];
moc.persistentStoreCoordinator = self.managedObjectContext.persistentStoreCoordinator;
// Register "current" MOC for notifications from "working" MOC
[[NSNotificationCenter defaultCenter] addObserver:managedObjectContext
selector:@selector(mergeChangesFromContextDidSaveNotification:)
name:NSManagedObjectContextDidSaveNotification
object:moc];
// Create a new controller stack
EmployeeController* e = [[EmployeeController alloc] initWithNibName:@"EmployeeController" bundle:nil];
UINavigationController* nc = [[UINavigationController alloc] initWithRootViewController:e];
// Prepare the "new" controller
e.managedObjectContext = moc;
e.employee = (Employee*) [NSEntityDescription insertNewObjectForEntityForName:@"Employee" inManagedObjectContext:moc];
e.modal = YES;
// Present the controller stack as a modal view
[self.navigationController presentModalViewController:nc animated:YES];
// Clean up
[nc release];
[e release];
[moc release];
}
The key bit of code is the addObserver:selector:name:object:
call, which captures notifications posted by the “scratchpad” MOC when and if it commits changes to the persistent store, and merges those changes into the observing MOC (i.e. the List
controller’s MOC). The controller can then use delegation (as discussed two weeks ago) to update the list view.