Tải bản đầy đủ (.pdf) (10 trang)

Phát triển ứng dụng cho iPhone và iPad - part 17 doc

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (2.81 MB, 10 trang )

Using Core Data: A Simple Task Manager

129
initWithManagedObjectModel:[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
to
NSInMemoryStoreType . Remember that most often you will be using the SQLite backing store.
CH005.indd 129CH005.indd 129 9/18/10 9:34:23 AM9/18/10 9:34:23 AM
130

CHAPTER 5 INTRODUCING CORE DATA

The 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.
CH005.indd 130CH005.indd 130 9/18/10 9:34:23 AM9/18/10 9:34:23 AM
Using Core Data: A Simple Task Manager

131
I 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.
CH005.indd 131CH005.indd 131 9/18/10 9:34:24 AM9/18/10 9:34:24 AM
132

CHAPTER 5 INTRODUCING CORE DATA
You 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

NSFetchedResultsController .
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

NSFetchedResultsController class works. 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
Fetch Request
Managed Object
Context
FIGURE 5 - 4: NSFetchedResultsController
usage
CH005.indd 132CH005.indd 132 9/18/10 9:34:24 AM9/18/10 9:34:24 AM
Using Core Data: A Simple Task Manager

133
Set 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
object, creates an entity based on the
“ Event ” entity in the context, and then sets the entity used by
CH005.indd 133CH005.indd 133 9/18/10 9:34:25 AM9/18/10 9:34:25 AM
134

CHAPTER 5 INTRODUCING CORE DATA
the 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
CH005.indd 134CH005.indd 134 9/18/10 9:34:25 AM9/18/10 9:34:25 AM
Using Core Data: A Simple Task Manager

135
The 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];
}

CH005.indd 135CH005.indd 135 9/18/10 9:34:26 AM9/18/10 9:34:26 AM
136

CHAPTER 5 INTRODUCING CORE DATA
// 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.
CH005.indd 136CH005.indd 136 9/18/10 9:34:27 AM9/18/10 9:34:27 AM
Using Core Data: A Simple Task Manager

137
- (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 > ;
};
CH005.indd 137CH005.indd 137 9/18/10 9:34:27 AM9/18/10 9:34:27 AM
138


CHAPTER 5 INTRODUCING CORE DATA
NSStoreModelVersionHashesVersion = 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
CH005.indd 138CH005.indd 138 9/18/10 9:34:28 AM9/18/10 9:34:28 AM

×