Core Data

Core Data is a framework and set of tools that allow you to persist your application’s data to the iPhone’s file system automatically. Core Data is a form of something called object-relational mapping, which is just a fancy way of saying that Core Data takes the data stored in your Objective-C objects and translates (or maps) that data into another form so that it can be easily stored in a database, such as SQLite, or into a flat file. But you don’t have to write SQL Queries etc.

Core Data Concepts & Terminology

High-level diagram of the Core Data architecture

Overview

The persistent store (or backing store or database) is a file on the iPhone’s file system that can be either a SQLite database or a binary flat file. A data model file, contained in one or more files with an extension of .xcdatamodel, describes the structure of your application’s data. This file can be edited in Xcode by double clicking. The Persistent Store Coordinator is used by other Core Data classes that need to save, retrieve, or search for data in the persistent store (your database or file).The data model tells the persistent store coordinator the format of all data stored in that persistent store. Objective C data model class is NSManagedObjectModel and instance of each entity in persistent store is called NSManagedObject. The state of Managed Objects is managed by ManagedObjectContext. If we want to save changes we ask the ManagedObjectContext to do that.

Persistent Store & Data Model

The persistent store, which is sometimes referred to as a (backing store or database) is where Core Data stores its data. By default on the iPhone, Core Data will use a SQLite database contained in your application’s documents folder as its persistent store.

Every persistent store is associated with a single, yes only ‘single’ Data Model, which defines the types of data that the persistent store can store. If you expand the Resources folder in the Groups & Files pane in Xcode, you’ll see a file called CoreData.xcdatamodel. That file is the default data model for your project. The project template with “Use Core Data For Storage” checked gives us a single persistent store and an associated data model.

The Data Model Class : NSManagedObjectModel

Although you won’t typically access your application’s data model directly, you should be aware of the fact that there is an Objective-C class that represents the data model in memory. This class is called NSManagedObjectModel, and the template automatically creates an instance of NSManagedObjectModel based on the data model file in your project.

In your project window’s Groups & Files pane, open the Classes group and single-click CoreDataAppDelegate.m.

Goto -managedObjectModel method which creates the object model based on the CoreData.xcdatamodel file.

The method should look like this:

/**

Returns the managed object model for the application.

If the model doesn’t already exist, it is created by merging all of the models

found in the application bundle.

*/

– (NSManagedObjectModel *)managedObjectModel {

if (managedObjectModel != nil) {

return managedObjectModel;

}

managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];

return managedObjectModel;

}

This method uses a form of lazy loading (doesn’t instantiate the ManagedObjectModel until the first time it is accessed)

The data model class is called NSManagedObjectModel because instances of data in Core Data are called Managed Objects. Makes sense right? 

<Optional reading> you can read this part later

Remember we said that a persistent store was associated with a single data model? Well, that’s true, but it doesn’t tell the whole story. You can combine multiple .xcdatamodel files into a single instance of NSManagedObjectModel, creating a single data model that combines all the entities from multiple files. This line of code takes any .xcdatamodel files that might be in your Xcode project and combines them together into a single instance of

NSManagedObjectModel: managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];

So, for example, if you were to create a second data model file and add it to your

project, that new file would be combined with CoreData.xcdatamodel into a single

managed object model that contained the contents of both files. This allows you to split up your application’s data model into multiple smaller and more manageable files. But lets just consider a single data model for now because most of the applications work this way. </Optional reading>

Persistent Store Coordinator (NSPersistentStoreCoordinator)

NSPersistentStoreCoordinator controls access to the persistent store. it takes all the calls coming from different classes that trigger reads or writes to the persistent store and serializes them so that multiple calls against the same file are not being made at the same time, which could result in problems due to file or database locking.

The template provides us with a method in the application delegate that creates and returns an instance of a persistent store coordinator.

In CoreDataAppDelegate.m, Goto -persistentStoreCoordinator .

Here’s the method:

/**

Returns the persistent store coordinator for the application.

If the coordinator doesn’t already exist, it is created and the application’s store

added to it.

*/

– (NSPersistentStoreCoordinator *)persistentStoreCoordinator {

if (persistentStoreCoordinator != nil) {

return persistentStoreCoordinator;

}

NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory]

stringByAppendingPathComponent: @”CoreData.sqlite”]];

NSError *error;

persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]

initWithManagedObjectModel: [self managedObjectModel]];

if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType

configuration:nil URL:storeUrl options:nil error:&error]) {

// Handle error

}

return persistentStoreCoordinator;

}

This method also uses lazy loading i.e doesn’t instantiate the persistent store coordinator until the first time its accessed, Then it creates a path to a file called CoreData.sqlite in the documents directory in your application’s sandbox. The template will always create a filename based on your project’s name. If you want to use a different name, you can change it here, though it generally doesn’t matter what you call the file, since the user will never see it. Hehe 😛

The most important line in this method is this one:

if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType

configuration:nil URL:storeUrl options:nil error:&error]) {

The first parameter to this method, NSSQLiteStoreType, determines the type of the

persistent store (yes the Database type). There are 3 types to store data:

  • NSSQLiteStoreType : Tells Core Data to use a SQLite database for its persistent store.
  • NSBinaryStoreType : If you want your application to use a single, binary flat file instead of a SQLite database for its persistent store.
  • NSInMemoryStoreType The primary use of this option is to create a caching mechanism, storing the data in memory instead of in a database or binary file for its persistent store.

Managed Objects (NSManagedObject)

Every instance of an entity that you work with in Core Data will be an instance of the class NSManagedObject or a subclass of NSManagedObject.

Like the NSDictionary class, NSManagedObject supports the key-value methods valueForKey: and setValue:forKey: for setting and retrieving attribute values. It also has additional methods for working with relationships. You can, for example, retrieve an instance of NSMutableSet representing a specific relationship. Adding managed objects to this mutable set, or removing them will add or remove objects from the relationship it represents.

In the template application, consider an instance of NSManagedObject that represents a single Event. We could retrieve the value stored in its timeStamp attribute by calling valueForKey:, like so:

NSDate *timeStamp = [managedObject valueForKey:@”timeStamp”];

Since timeStamp is an attribute of type date, we know the object returned by

valueForKey: will be an instance of NSDate. Similarly, we could set the value using

setValue:ForKey:. The following code would set the timeStamp attribute of

managedObject to the current date and time:

[managedObject setValue:[NSDate date] forKey:@”timeStamp”];

Key Value Coding also includes the concept of a keypath. Keypaths allow you iterate through object hierarchies using a single string. So, for example, if we had a relationship on our Employee entity called whereIWork, which pointed to an entity named Employer, and the Employer entity had an attribute called name, then we could get to the value stored in name from an instance of Employee using a keypath like so:

NSString *employerName = [managedObject valueForKeyPath:@”whereIWork.name”];

Managed Objects Context (NSManagedObjectContext)

Core Data maintains an object that acts as a gateway between your entities and the rest of Core Data. That gateway is called a Managed Object Context (often just referred to as a context). The context maintains state for all the managed objects that you’ve loaded or created. The context keeps track of changes that have been made since the last time a managed object was saved or loaded. When you want to load or search for objects, for example, you do it against a context. When you want to commit your changes to the persistent store, you save the context. If you want to undo changes to a managed object, you just ask the managed object context to undo. (Yes, it even handles all the work needed to implement undo and redo for your data model.)

Because every application needs at least one (there can be more than one but most of the time its just one) managed object context to function, the template has very kindly provided us with one. Click CoreDataAppDelegate.m again, and Goto –managedObjectContext .

You will see a method that looks like this:

/**

Returns the managed object context for the application.

If the context doesn’t already exist, it is created and bound to the persistent

store coordinator for the application.

*/

– (NSManagedObjectContext *) managedObjectContext {

if (managedObjectContext != nil) {

return managedObjectContext;

}

NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];

if (coordinator != nil) {

managedObjectContext = [[NSManagedObjectContext alloc] init];

[managedObjectContext setPersistentStoreCoordinator: coordinator];

}

return managedObjectContext;

}

This method is actually pretty straightforward. Using lazy loading, managedObjectContext is checked for nil. If it is not nil, its value is returned. If managedObjectContext is nil, we check to see if our NSPersistentStoreCoordinator exists. If so, we create a new managedObjectContext, then use setPersistentStoreCoordinator: to tie the current coordinator to our managedObjectContext. When we’re finished, we return managedObjectContext.

Note that managed object contexts do not work directly against a persistent store; they go through a persistent store coordinator. As a result, every managed object context needs to be provided with a pointer to a persistent store coordinator in order to function.

Fetch Request : Which entity to fetch

Sort Descriptor : Determines the order how data should be organized

Fetched Requests Controller (NSFetchedResultsController)

Most of the generic controller classes in the iPhone SDK — such as UINavigationController, UITableViewController, and UIViewController—are designed to act as the controller for a specific type of view. View controllers, however, are not the only types of controller classes that Cocoa Touch provides, although they are the most common. NSFetchedResultsController is an example of a controller class that is not a view controller.

NSFetchedResultsController is designed to handle one very specific job, which is to manage the objects returned from a Core Data fetch request. NSFetchedResultsController makes displaying data from Core Data easier than it would otherwise be, because it handles a bunch of tasks for you. It will, for example, purge any unneeded objects from memory when it receives a low-memory warning and reload them when it needs them again. If you specify a delegate for the fetched results controller, your delegate will be notified when certain changes are made to its underlying data.

Lets now see the following topics:

  • Creating the Fetched Results Controller
  • Fetched Results Controller Delegate Methods

Creating a Fetched Results Controller

Let’s take a closer look at the creation of the fetched results controller. In RootViewController.m, Goto the method – fetchedResultsController.

It looks like this:

– (NSFetchedResultsController *)fetchedResultsController {

if (fetchedResultsController != nil) {

return fetchedResultsController;

}

/*

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;

}

You start by creating a fetch request (the entity to fetch) and then use that fetch request to create a fetched results controller. In our template, this is done in RootViewController.m, in the fetchedResultsController method.

fetchedResultsController starts by creating a new fetch request. A fetch request is basically a specification that lays out the details of the data to be fetched. You’ll need to tell the fetch request which entity to fetch. In addition, you’ll want to add a sort descriptor to the fetch request. The sort descriptor determines the order in which the data is organized.

Once the fetch request is complete, the fetched results controller is created. The fetched results controller is an instance of the class NSFetchedResultsController. Remember that the fetched results controller’s job is to use the fetch request to keep its associated data as fresh as possible. Once the fetched results controller is created, you’ll do your initial fetch. We do this in RootViewController.m at the end of ViewDidLoad, by sending our fetched results controller the PerformFetch message.

Now that you have your data, you’re ready to be a data source and a delegate to your table view. When your table view wants the number of sections for its table, it will call numberOfSectionsInTableView:.

In our version, we get the section information by passing the appropriate message to fetchResultsController.

Here’s the version from RootViewController.m:

– (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {

return [[fetchedResultsController sections] count];

}

The same strategy applies in tableView:numberOfRowsInSection:

– (NSInteger)tableView:(UITableView *)tableView

numberOfRowsInSection:(NSInteger)section {

id <NSFetchedResultsSectionInfo> sectionInfo =

[[fetchedResultsController sections] objectAtIndex:section];

return [sectionInfo numberOfObjects];

}

Fetched Results Controller Delegate Methods

  1. Will Change Content Delegate Method

– (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {

[self.tableView beginUpdates];

}

  1. Did Change Content Delegate Method

– (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {

[self.tableView endUpdates];

}

  1. Did Change Object Delegate Method

When the fetched results controller notices a change to a specific object, it will notify its delegate using the method controller:didChangeObject:forChangeType:newIndexPath:.

This method is where you need to handle updating, inserting, deleting, or moving rows in your table view to reflect whatever change was made to the objects managed by the fetched results controller.

– (void)controller:(NSFetchedResultsController *)controller

didChangeObject:(id)anObject

atIndexPath:(NSIndexPath *)indexPath

forChangeType:(NSFetchedResultsChangeType)type

newIndexPath:(NSIndexPath *)newIndexPath {….}

  1. Did Change Section Delegate Method

Lastly, if a change to an object affects the number of sections in the table, the fetched results controller will call the delegate method controller:didChangeSection:atIndex:forChangeType:.

If you specify a sectionNameKeyPath when you create your fetched results controller, you need to implement this delegate method to take care of adding and deleting sections from the table as needed. If you don’t, you will get runtime errors when the number of sections in the table doesn’t match the number of sections in the fetched results controller.

– (void)controller:(NSFetchedResultsController *)controller

didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo

atIndex:(NSUInteger)sectionIndex

forChangeType:(NSFetchedResultsChangeType)type {

Once you’ve implemented these four delegate methods, if you add a new managed

object, the fetched results controller will detect that, and your table will be updated

automatically. If you delete or change an object, the controller will detect that, too. Any change that affects the fetched results controller will automatically trigger an appropriate update to the table view, including properly animating the process. This means that you don’t need to litter your code with calls to reloadData every time you make a change that might impact your dataset.

Saves on Terminate Method

Let’s scroll up to another method called applicationWillTerminate inside CoreDataAppDelegate.m which saves changes to the context if any have been

made. The changes are saved to the persistent store. As its name implies, this method is called just before the application exits.

/**

applicationWillTerminate: saves changes in the application’s managed object context

before the application terminates.

*/

– (void)applicationWillTerminate:(UIApplication *)application {

NSError *error;

if (managedObjectContext != nil) {

if ([managedObjectContext hasChanges] && ![managedObjectContext

save:&error]) {

// Handle error.

NSLog(@”Unresolved error %@, %@”, error, [error userInfo]);

exit(-1); // Fail

}

}

}

Retrieving a Managed Object From Fetched Results Controller

Our table view delegate methods became much shorter and more straightforward, since our fetched results controller does much of the work that we previously did in those methods. For example, to retrieve the object that corresponds to a particular cell, which we often need to do in

tableView:cellForRowAtIndexPath: and tableView:didSelectRowAtIndexPath:,

we can just call objectAtIndexPath: on the fetched results controller and pass in the indexPath parameter, and it will return the correct object:

NSManagedObject *managedObject = [fetchedResultsController objectAtIndexPath:indexPath];

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s