- Persisting Objects to Disk
- The Core Data Approach
- Examining the Xcode Core Data Templates
- Summary
Examining the Xcode Core Data Templates
Now that you have a better idea of Core Data terminology, let's take a look inside a standard Core Data template project for an iOS application to see how all of this works in practice.
The Navigation-Based Project Template
The Xcode project templates for the Navigation-based, Split View-based, Utility and Window-based applications all offer the option to Use Core Data, as shown in Figure 2.3.
Figure 2.3 The New Project Window
To follow the rest of this chapter, launch Xcode and choose File > New > New Project (Shift--N), then select the Navigation-based Application template. Call the project TemplateProject, and tick the Use Core Data checkbox.
When the project window appears, you'll see that there are some extra items compared to a standard project. First, the project links to the CoreData.framework. Second, there is an item called TemplateProject.xcdatamodeld. This defines the structure of your data model, which you build visually using the Xcode Data Modeler. Click on the file to open it.
There are two ways to view a data model in Xcode 4—as a Table or a Graph, as shown in Figure 2.4.
Figure 2.4 The two Editor Styles in the Xcode 4 Data Modeler
The Data Modeler
At the top-left of the editor, you'll see a list of Entities. In the template file, there is a single entity, called Event. If you select this entity for display using the Table editor style, you'll see a list of the entity's attributes, relationships and fetched properties.
The Event entity has a single attribute listed, called timeStamp. If you click this attribute to select it, and look in Xcode 4's Data Model Inspector (Option--3), you'll see that its Type is set to Date.
This inspector offers a number of other options relating to that attribute. For example, this is where you can choose to validate the data that is stored, or mark an attribute as being optional; these options are covered in Chapter 3, "Modeling Your Data."
Xcode 4's Graph editor style offers a visual representation of the entities in your object model. At present, there is only a single entity in the model, but if there were more than one, you would see the relationships between the entities represented by lines and arrows connecting the two, as in Figure 2.5.
Figure 2.5 Relationships between objects in the Object Graph
Setting up the Core Data Stack
When working with data held in a persistent store, you will need to have built up a stack of objects; at the bottom is the actual persistent store on disk, then comes a persistent store controller to liaise between the store and the next level, the managed object context, as shown in Figure 2.6.
Figure 2.6 The Core Data Stack
It's also possible to have more than one persistent store under the one coordinator, as well as multiple managed object contexts (as discussed earlier in the chapter).
Let's examine the template code provided to set up this Core Data stack. Open the TemplateProjectAppDelegate.h interface file, and you'll find that there are a number of property declarations defined for the app delegate, as shown in Listing 2.5 (the Xcode 4 templates make use of the modern runtime feature of synthesizing the corresponding instance variables).
Listing 2.5. The app delegate interface
@interface TemplateProjectAppDelegate : NSObject <UIApplicationDelegate> { } @property (nonatomic, retain) IBOutlet UIWindow *window; @property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext; @property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel; @property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator; - (void)saveContext; - (NSURL *)applicationDocumentsDirectory; @property (nonatomic, retain) IBOutlet UINavigationController *navigationController; @end
The app delegate keeps track of the managed object model, which is the information contained within the TemplateProject.xcdatamodeld file. It also has a reference to a persistent store coordinator, along with a managed object context. The applicationDocumentsDirectory is used to determine where the data will be held on disk.
Switch to the TemplateProjectAppDelegate.m interface file and scroll through the various methods that are provided. Near the bottom, you'll find the applicationDocumentsDirectory method. As the name implies, it simply returns the path to the application's documents directory.
Next find the persistentStoreCoordinator method. This method sets up a persistent store coordinator, to access a SQLite store file called TemplateProject.sqlite, located in the application's documents directory. The persistent store is initialized using the model provided by the managedObjectModel method, so jump to this method next.
The managedObjectModel method returns an NSManagedObjectModel object created from a file called TemplateProject.momd. When you compile the project, the TemplateProject.xcdatamodeld data model is compiled into this .momd resource and stored in the application's bundle.
It's also possible to create an NSManagedObjectModel by merging all the available model files using the class method mergedModelFromBundles:, or by merging selected models using modelByMergingModels:. Although there is only a single model file (it's actually a model bundle) in this template application, it is possible to split your model into multiple .xcdatamodeld files, if you wish.
At the top of the stack, you'll find the managedObjectContext method. This method sets up the context using the persistent store coordinator. If you look in the awakeFromNib method, towards the top of the file, you'll find that it sets a property on the RootViewController for a managedObjectContext. It is at this point that the chain is triggered to set up the managed object model, persistent store coordinator and finally the context.
Lastly, the applicationWillTerminate: method calls a saveContext method, which checks to see whether any changes have been made to objects in the managed object context, and tries to save those changes if so. This means that the persistent store will be updated with changes from the context when the application exits. Some versions of the project template also call saveContext from applicationDidEnterBackground:, so that the store will be updated if the user switches to a different application under iOS 4 multitasking.
You probably won't need to modify the code in these methods unless you need to work with multiple stores or custom store types. Once the managed object context has been set up, it's passed to the root view controller. This view controller has the code that actually performs the relevant fetches to display the data in a table view, and it's this sort of code that you will typically be writing most of the time when you're working with Core Data. In Chapter 4, "Basic Storing and Fetching," you'll start writing your own code to populate a table view with information from a Core Data store.
Running the Application
To see the functionality you get from the basic project template, build and run the application. You'll find that you can add to a list of Events; the table view shows these, outputting the values of the events' timeStamp attributes. Note that you can remove items from the table view using the Edit button, or by swiping your finger across a row.
A Quick Look at the RootViewController Code
To get an idea of how this all works, open up the RootViewController.m implementation file. The template application makes use of a Fetched Results Controller to simplify working with fetched objects and table views. This object is created lazily and told to fetch when it's needed by one of the table view data source methods.
Figure 2.7 The Template Application in the Simulator
If you look at the code that generates the fetchedResultsController lazily, you'll see that it's set to use the Event entity and given sort descriptors to sort by the timeStamp attribute.
The methods to display the contents in the table view are the standard table view data source methods; for the numberOfSections... and numberOfRows... methods, the template code just queries the fetched results controller object. To display the information in the cellForRowAtIndexPath: method, a configureCell:atIndexPath: method is used. Notice how simple it is to get hold of the managed object at the selected index. The code to display the time stamp is shown in Listing 2.6.
Listing 2.6. Displaying the timestamp in the cell
NSManagedObject *managedObject = [self.fetchedResultsController objectAtIndexPath:indexPath]; cell.textLabel.text = [[managedObject valueForKey:@"timeStamp"] description];
The fetched results controller returns the object at the specified index, and the code just queries that returned object for the description of its timeStamp key.
The commitEditingStyle:forRowAtIndexPath: method simply tells the managed object context to delete the object at the specified index path, and then asks the context to save. This will mean that the object deleted from the table view will also be deleted from the persistent store.
Lastly, find the insertNewObject method that gets called when the user tries to add an object to the table view, and you'll see that the procedure is:
- Get a pointer to a managed object context.
- Decide which entity you need to use to create a new object.
- Insert a new object for that entity, into the managed object context.
- Set the relevant values on the new object.
- Tell the context to save.
When the context saves, the new object is written to the persistent store. It's as simple as that! Note that the template files are deliberately verbose—it's common to accomplish the above using only two or three lines of code.