Typical reasons for an error here include: * The persistent store is not accessible * The schema for the persistent store is incompatible with current managed object model Check the e
Trang 1initWithManagedObjectModel:[self managedObjectModel]];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl
options:nil error: & error]) { /*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate.
You should not use this function in a shipping application, although
it may be useful during development If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
Typical reasons for an error here include:
* The persistent store is not accessible
* The schema for the persistent store is incompatible with current managed object model
Check the error message to determine what the actual problem was.
*/
NSLog(@”Unresolved error %@, %@”, error, [error userInfo]);
abort();
} return persistentStoreCoordinator;
}
TasksAppDelegate.m
The code fi rst determines if the Persistent Store Coordinator already exists If it exists, the method returns it to you
If the coordinator does not exist, the code must create it In order to create the coordinator, you need
to pass in a URL that points to the data store The template uses the fileURLWithPath: method on the NSURL class to create the URL You can see that the store name will be Tasks.sqlite
Next, the code allocates an NSError object to hold the error data that the Persistent Store Coordinator generates if there is a problem confi guring the coordinator
The next line allocates and initializes the coordinator using the Managed Object Model You will look at the managedObjectModel getter method in a moment Remember that you use the Persistent Store Coordinator to mediate between the Managed Object Context, the Managed Object Model, and the data store Therefore, it makes sense that the coordinator would need to have a reference to the model
Now that the coordinator knows about the model, the code goes on to tell it about the data store
The next line of code adds the data store to the coordinator You will notice that the type
of the data store is NSSQLiteStoreType This indicates that the template uses the SQLite backing data store If you wanted to use the binary store, you would change this enumeration value to
NSBinaryStoreType , and if you wanted to use the in - memory store, you would set the value
Trang 2The rest of the code logs an error if there was a problem adding the SQLite store to the coordinator
If there was no error, the method returns the coordinator
The getter function to return the Managed Object Model is very straightforward:
- (NSManagedObjectModel *)managedObjectModel {
if (managedObjectModel != nil) {
return managedObjectModel;
}
managedObjectModel = [[NSManagedObjectModel
mergedModelFromBundles:nil] retain];
return managedObjectModel;
}
TasksAppDelegate.m
If the model already exists, the getter method returns it If not, the code creates a new Managed Object
Model by merging all of the model fi les contained in the application bundle The Tasks.xcdatamodel
fi le in the Resources folder contains the object model This fi le is included with the application bundle,
so this method takes that fi le and uses it to create the NSManagedObjectModel object
The last bit of interesting code in the App Delegate is the managedObjectContext getter method:
- (NSManagedObjectContext *) managedObjectContext {
if (managedObjectContext != nil) {
return managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator =
[self persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext = [[NSManagedObjectContext alloc] init];
[managedObjectContext setPersistentStoreCoordinator: coordinator];
}
return managedObjectContext;
}
TasksAppDelegate.m
This method works similarly to the previous two The fi rst thing it does is check to see if the context
already exists If the context exists, the method returns it
Next, the code gets a reference to the persistentStoreCoordinator If you refer back to
Figure 5 - 1, you will see that the Managed Object Context needs only a reference to the Persistent
Store Coordinator
If the code successfully gets a reference to the coordinator, it goes on to create the context Then, the
code sets the coordinator for the context and returns the context
Trang 3I hope that the way in which all of the objects in the Core Data stack fi t together is becoming a bit clearer Remember that you did not have to write any code to create the data store, the Persistent Store Coordinator, the Managed Object Model, or the Managed Object Context The template code takes care of all of this for you automatically You will see that the only interface that you need to deal with is that of the Managed Object Context
The Data Model
Before you examine the RootViewController and how to use it to create your TableView using Core Data, let ’ s look at the template Managed Object Model If you open the Resources folder in Xcode and double - click on Tasks.xcdatamodel , you should see something similar to Figure 5 - 3
FIGURE 5 - 3: The default Object Model
The code template creates a default entity called “ Event ” The blue highlighting and resize handles indicate that the Event entity is selected Click anywhere else in the diagram to de - select the Event entity and it will turn pink to indicate that it is no longer selected Click on the Event entity again to select it
There are three panes in the top of the data - modeling tool From left to right they are the Entities pane, the Properties pane, and the Detail pane The Entities pane provides a list of all of the entities
in your model The Properties pane lists the properties of the currently selected entity, and the Detail pane shows details related to whatever is currently selected, either an entity or property The bottom portion of the tool displays a graphical representation of your model called the Diagram View The next chapter provides more detail on using the data - modeling tool
Trang 4You can see in Figure 5 - 3 that the Event entity has an attribute called timeStamp If you select the
Event entity and then select the timeStamp property in the Properties pane, you will see the details
of the timeStamp attribute in the Detail pane You should see that the timeStamp attribute is
optional and that it is a Date type
Your managed object context will manage the objects defi ned by the Event entity Remember that
when you created the Persistent Store Coordinator, you initialized it with all of the managed object
models in the bundle Then, when you created the context, it used the Persistent Store Coordinator
Next, you will see how you use the Event entity to create and manage Core Data Managed Objects
in code
RootViewController
The RootViewController is a subclass of UITableViewController and contains the TableView
that you will use to display your data This class has a property that holds the context,
which the Application Delegate sets when it creates the RootViewController instance The
RootViewController also has a property that holds an
The NSFetchedResultsController class is the
glue that binds the results of a fetch request against
your datasource to a TableView You will look at the
NSFetchedResultsController class in more detail in
Chapter 6
Figure 5 - 4 shows a high - level view of how the
class takes a fetch request and a context as its inputs
and calls delegate methods when the data in the fetch
request changes The controller implements methods
that you use when implementing the TableView
delegate methods that you are familiar with from
Chapter 3
The fi rst thing to notice is that the RootViewController implements the
NSFetchedResultsControllerDelegate protocol You can see this in the RootViewController.h
header fi le Remember that declaring that you implement a protocol is a contract that commits you
to implementing certain methods Classes that do not implement this protocol cannot be delegates
for an NSFetchedResultsController
The following is the code for the getter method for the fetchedResultsController property:
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
/*
NSFetchedResultsController
Fetched Results Controller Delegate
Context
FIGURE 5 - 4: NSFetchedResultsController usage
Trang 5Set up the fetched results controller.
*/
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:@”Event”
inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@”timeStamp”
ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means “no sections”.
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:@”Root”];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[aFetchedResultsController release];
[fetchRequest release];
[sortDescriptor release];
[sortDescriptors release];
return fetchedResultsController;
}
RootViewController.m
The fi rst part of the code should be familiar It checks to see if you have already created the
fetchedResultsController If it already exists, the method returns the controller If not, the code goes on to create it
The next section of code creates and confi gures the objects needed by the
fetchedResultsController As you can see from Figure 5 - 4, you need a fetch request and a context to be able to use the fetchedResultsController Because you already have a context, in the managedObjectContext property, the code only needs to create a fetch request
You can think of a fetch request as a SQL SELECT statement The code creates a FetchRequest
Trang 6the fetchRequest Next, the code sets the batch size of the fetchRequest to a reasonable number
of records to receive at a time
The next bit of code creates an NSSortDescriptor You use the NSSortDescriptor to sort
the results in the fetchRequest You can think of the NSSortDescriptor as a SQL ORDER BY
clause Here, you order the result set based on the timeStamp fi eld in descending order The
NSSortDescriptor then sets the sort descriptor used by the fetch request
Finally, calling the initWithFetchRequest:managedObjectContext:sectionNameKeyPath:
cacheName: method creates and initializes fetchedResultsController The template then sets the
delegate to self , assigns the fetchedResultsController property, and then the template releases
the local objects
The only delegate method from the NSFetchedResultsControllerDelegate protocol implemented
by the template is controllerDidChangeContent: The fetchedResultsController calls this
method when all changes to the objects managed by the controller are complete In this case, you
tell the table to reload its data
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
// In the simplest, most efficient, case, reload the table view.
[self.tableView reloadData];
}
RootViewController.m
Now that you have seen how you create and confi gure the fetchedResultsController , let ’ s look
at how you confi gure the RootViewController at startup You do this in the viewDidLoad method:
- (void)viewDidLoad {
[super viewDidLoad];
// Set up the edit and add buttons.
self.navigationItem.leftBarButtonItem = self.editButtonItem;
UIBarButtonItem *addButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
target:self
action:@selector(insertNewObject)];
self.navigationItem.rightBarButtonItem = addButton;
[addButton release];
NSError *error = nil;
if (![[self fetchedResultsController] performFetch: & error]) {
// Log the error
NSLog(@”Unresolved error %@, %@”, error, [error userInfo]);
// Quit
abort();
}
}
RootViewController.m
Trang 7The method fi rst calls the superclass version of viewDidLoad to ensure that you perform any initialization required by the superclass
Next, the code confi gures the Edit button, creates the Add button and adds the buttons to the navigation at the top of the screen You can see in the initialization of the addButton that the insertNewObject method will be called when someone taps the Add button
Finally, you call the performFetch: method on the fetchedResultsController to execute the fetch request and retrieve the desired data
Now, you will look at how you use the TableView delegate methods to display your data These should be familiar to you from Chapter 3
The fi rst method is numberOfSectionsInTableView :
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return [[fetchedResultsController sections] count];
}
RootViewController.m
As you may recall, the TableView calls this method when it needs to know how many sections to display Here, the code simply asks the fetchedResultsController for the number of sections
The next TableView delegate method is numberOfRowsInSection :
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
id < NSFetchedResultsSectionInfo > sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
RootViewController.m
Again, you call upon the fetchedResultsController to return the number of rows to display
Finally, you confi gure the cell in the cellForRowAtIndexPath : method
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @”Cell”;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) { cell = [[[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
Trang 8// Configure the cell.
NSManagedObject *managedObject = [fetchedResultsController
objectAtIndexPath:indexPath];
cell.textLabel.text =
[[managedObject valueForKey:@”timeStamp”] description];
return cell;
}
RootViewController.m
This code should be familiar down to the point where it retrieves the managed object Again, the
code asks fetchedResultsController for the object pointed to by the index path Once it obtains
this object, the code uses key - value coding to get the value for the timeStamp property You learn
more about key - value coding in Chapter 6
The commitEditingStyle:forRowAtIndexPath: method contains the code to handle editing rows
in the TableView The TableView calls this method when editing of the TableView will cause a
change to the underlying data In this case, deleting an object in the TableView should delete the
object from the context
- (void)tableView:(UITableView *)tableView
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the managed object for the given index path
NSManagedObjectContext *context =
[fetchedResultsController managedObjectContext];
[context deleteObject:
[fetchedResultsController objectAtIndexPath:indexPath]];
// Save the context.
NSError *error = nil;
if (![context save: & error]) {
NSLog(@”Unresolved error %@, %@”, error, [error userInfo]);
abort();
}
}
}
RootViewController.m
The code fi rst determines if the user has deleted a cell Then, it gets a reference to the context and
tells the context to delete the object that was deleted from the TableView Last, the context changes
are committed to disk by calling the save: method
The last interesting bit of code in the RootViewController is the insertNewObject method Recall
that this is the method that will be called when a user taps the Add button at the top of the screen
Trang 9- (void)insertNewObject {
// Create a new instance of the entity managed by the fetched results // controller.
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[fetchedResultsController fetchRequest] entity];
NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name]
inManagedObjectContext:context];
// If appropriate, configure the new managed object.
[newManagedObject setValue:[NSDate date] forKey:@”timeStamp”];
// Save the context.
NSError *error = nil;
if (![context save: & error]) { NSLog(@”Unresolved error %@, %@”, error, [error userInfo]);
abort();
} }
RootViewController.m
Like the commitEditingStyle:forRowAtIndexPath: method, this code fi rst gets a reference to the context Next, the code creates a new entity based on the entity that the
fetchedResultsController uses The code then creates a new managed object based on that entity and inserts it into the context Next, the code confi gures the managed object with the appropriate data; in this case, a timestamp Finally, the context is committed to disk with the save: method
Modifying the Template Code
Now that you are familiar with how the template code works, you will modify it to create tasks instead of timestamps To build the task application, you will modify the data model by creating
a Task entity, create a new ViewController that you will use to create tasks, and update the
RootViewController to use the new Task entity
The fi rst thing that you will do is to modify the data model by changing the existing Event entity to make it a Task entity If you make a change to the data model and attempt to run your application, you will get an error that looks something like this:
2010-03-31 12:50:34.595 Tasks[2424:207] Unresolved error Error Domain=NSCocoaErrorDomain Code=134100 UserInfo=0x3d12140
“Operation could not be completed (Cocoa error 134100.)”, { metadata = {
NSPersistenceFrameworkVersion = 248;
NSStoreModelVersionHashes = { Location = < fa099c17 c3432901 bbaf6eb3 1dddc734 a9ac14d2 36b913ed 97ebad53 3e2e5363 > ;
Task = < 40414517 78c0bd9f 84e09e2a 91478c44 d85394f8 e9bb7e5a abb9be27 96761c30 > ;
Trang 10NSStoreModelVersionHashesVersion = 3;
NSStoreModelVersionIdentifiers = (
);
NSStoreType = SQLite;
NSStoreUUID = “762ED962-367C-476C-B4BD-076A6D1C33A9”;
“_NSAutoVacuumLevel” = 2;
};
reason = “The model used to open the store is incompatible with the one
used to create the store”;
}
RootViewController.m
This error says that “ The model used to open the store is incompatible with the one used to create
the store ” This means exactly what it sounds like: The data store on the device is not compatible
with your revised data model, so Core Data cannot open it When you encounter this situation, you
will need to use Core Data migration to move your data from one data model to another Core Data
migration is covered in detail in Chapter 9 For now, simply delete the existing application from the
simulator or device before trying to run it again This will force Core Data to build a new data store
that is compatible with the data model
Open the Tasks.xcdatamodel fi le, and then select the Event entity Change the name of the entity
to “ Task ” in the Detail pane Add a new property to the Task entity by clicking the plus icon below
the Properties pane and selecting Add Attribute Call the attribute taskText and set its type to
String in the Properties pane You will use the new attribute to store the text for your tasks Your
data model should look like Figure 5 - 5
FIGURE 5 - 5: Revised Tasks data model