Core Data Primer - iOS Components and Frameworks: Understanding the Advanced Features of the iOS SDK (2014)

iOS Components and Frameworks: Understanding the Advanced Features of the iOS SDK (2014)

Chapter 12. Core Data Primer

For many apps, being able to locally store and retrieve data that persists beyond a single session is a requirement. Since iOS 3.0, Core Data has been available to address this need. Core Data is a powerful object database; it provides robust data storage and management capabilities.

Core Data has its roots in NeXT’s Enterprise Object Framework (EOF), which was capable of mapping objects to relational databases. There are great advantages to writing business logic to objects, and not having to build database or persistence-specific logic. Mainly, there is a lot less code to write, and that code tends to be focused on the needs of the app rather than the needs of the database. EOF could support several brands of relational databases. Since Core Data was built to support single-user applications in Mac OS X, Core Data supports storing data in an embedded relational database called SQLite, which provides the benefits of an SQL database without the hassle and overhead of maintaining a database server.

Some features of Core Data include the following:

Image Modeling data objects with a visual model editor.

Image Automatic and manual migration tools to handle when object schema changes.

Image Establishing relationships between objects (one-to-one, one-to-many, many-to-many).

Image Storing data in separate files and different file formats.

Image Validation of object attributes.

Image Querying and sorting data.

Image Lazy loading data.

Image Interacting closely with iOS table views.

Image Managing related object changes with commit and undo capabilities.

Many books have been written solely about Core Data; this chapter provides a brief primer on the pieces needed to integrate and utilize Core Data in an app, and some of the basic capabilities of Core Data to assist in deciding whether it is the right fit for an app. Since this chapter does not explain how to use Core Data, there is no sample app. Refer to Chapter 13, “Getting Up and Running with Core Data,” for a sample app and information on how to get started with Core Data.

Deciding on Core Data

There are a few options available to iOS developers who would like to use persistent data:

Image NSUserDefaults: This method is typically used to save app preferences. NSUserDefaults functions very much like an NSDictionary with key-value storage, and supports storing values that can be expressed as NSNumber, NSString, NSDate, NSData, NSDictionary,NSArray, or any object that conforms to the NSCoding protocol. If an app’s persistence needs can be satisfied using key-value pairs, dictionaries, and arrays, then NSUserDefaults is a viable option.

Image Property List (plist): NSDictionary and NSArray each support reading from and saving to a user-specified property list file, which is an XML file format supporting NSNumber, NSString, NSDate, NSData, NSDictionary, and NSArray. If an app’s persistence needs can be satisfied using a dictionary or an array, then a property list file is a viable option.

Image Coders and Keyed Archives: NSCoder and NSKeyedArchiver support saving an arbitrary object graph into a binary file. These options require implementing NSCoder methods in each custom object to be saved, and require the developer to manage saving and loading. If an app’s persistence needs can be satisfied with a handful of custom objects, the coder/archiver approach is a viable option.

Image Direct SQLite: Using the C library libsqlite, apps can interact with SQLite databases directly. SQLite is an embedded relational database that does not need a server; it supports most of the standard SQL language as described by SQL92. Any data persistence logic that can be built using SQL can likely be built into an iOS app utilizing SQLite, including defining database tables and relationships, inserting data, querying data, and updating and deleting data. The drawback of this approach is that the app needs to map data between application objects and SQL files, requires writing SQL queries to retrieve and save data, and requires code to track which objects need to be saved.

Image Core Data: Provides most of the flexibility of working with SQLite directly, while insulating the app from the mechanics of working with the database. If the app requires more than a handful of data, needs to maintain relationships between different objects, or needs to be able to access specific objects or groups of objects quickly and easily, Core Data might be a good candidate.

To provide details on how Core Data is used in a typical app, this chapter first walks through using the classes that an app will typically interact with. Managed objects, or instances of NSManagedObject, are things that get stored. The managed object context (NSManagedObjectContext) is the working area that an app uses to create, query, and save managed objects. After describing these, this chapter explains the environment setup that allows Core Data to work.

Core Data Managed Objects

This section provides an overview of core data managed objects, including what managed objects are, how they are defined in a managed object model, how an app can interact with managed objects, and how changes to the definitions of the managed objects can be handled between app versions.

Managed Objects

Managed objects, or instances of NSManagedObject, are what an app will interact with the most. A managed object can be thought of as a dictionary with a known set of keys, and with known types of objects (like string or number) by key. Attributes for managed objects can always be accessed with this technique:

NSString *movieName = [myMovie valueForKey:@"movieName"];

And attributes can always be updated using setValue:forKey:

[myMovie setValue:@"Casablanca" forKey:@"movieName"];

Managed objects can be defined as subclasses of NSManagedObject, which will allow accessing the attributes as properties:

[myMovie setMovieName:@"Casablanca"];

NSString *movieName = [myMovie movieName];

Subclasses of NSManagedObject can have custom methods. For example, if there is a managed object to keep information about a movie, a custom method could be added to help keep track of how many times a movie has been watched. The method might increment the “times watched” attribute on the movie, and set the “last watched date” to today.

Managed objects can have relationships set up between them. For example, if an app tracks a movie collection, it might be useful to track if a movie has been lent to a friend. That can be modeled in the app by creating a Movie object and a Friend object, and then setting a relationship between the Movie and Friend objects.

Relationships between objects can be one-to-one. If the movie app were to store one poster image for each movie in a separate object, the movie and poster image could be related to each other:

[myMovie setValue:posterImageObject forKey:@"posterImage"];

NSManagedObject *movie =
[posterImageObject valueForKey:@"relatedMovie"];

Or a relationship can be one-to-many; for example, a movie can be lent to only one friend at a time, but a friend might be borrowing many movies at a time:

[myMovie setValue:myFriend forKey:@"lentToFriend"];

[myOtherMovie setValue:myFriend forKey:@"lentToFriend"];

NSSet *borrowedMovies =
[myFriend valueForKey:@"borrowedMovies"];

Managed Object Model

Managed objects are defined in a managed object model (NSManagedObjectModel). The managed object model includes the list of entities, the attributes associated with each entity, validations associated with attributes and entities, and the relationships established between the entities. Managed object models are typically created using Xcode’s visual modeling editor (see Figure 12.1).

Image

Figure 12.1 Xcode Data Model Editor: Graph style.

In Figure 12.1, two entities are represented. Each entity has attributes associated with it, and there is a one-to-many relationship set up between the two entities. To see more detail about the attributes for each entity, switch to the Table editing style using the Editor Style switch in the lower-right corner (see Figure 12.2).

Image

Figure 12.2 Xcode Data Model Editor: Table style.

Figure 12.2 shows all the attributes for the Movie entity, and the type of each attribute. The data types that Core Data supports are displayed in Table 12.1.

Image

Table 12.1 Core Data Supported Data Types

Core Data supports entity inheritance. Any entity can have one parent entity specified (see Figure 12.3). The child entity will inherit all the characteristics of the parent entity, including attributes, validations, and indexes.

Image

Figure 12.3 Xcode Data Model Editor: Parent Entity selection.

Managed Object Model Migrations

If an object model ever needs to change (even just adding one attribute to an entity is considered a change to the model), model versioning will need to be handled. When loading an existing persistent store, Core Data will check it against the object model to make sure it matches, using a hash calculated on the entities and attributes to determine what constitutes a unique object model. If it does not match, data from the persistent store will need to be migrated to a new persistent store based on the new object model. Core Data can handle many simple migrations automatically with options set in the persistent store coordinator (described later in the chapter), but if any logic is required, a migration might need to be written. Handling migrations is a complex topic; for more information on migrations, refer to the book Core Data for iOS: Developing Data-Driven Applications for the iPad, iPhone, and iPod Touch, by Tim Isted and Tom Harrington (Addison-Wesley Professional).

Creating Managed Objects

Managed objects can exist only in a managed object context (NSManagedObjectContext), which is Core Data’s working area. A managed object must be either created in or fetched from the managed object context. To create a new managed object, a reference to the managed object context is needed. The managed object context is available as a property on the application delegate for projects that have been set up using Xcode’s Master Detail project template. In addition, Core Data needs to know what entity the new managed object is for. Core Data has a class calledNSEntityDescription that provides information about entities. Create a new instance using NSEntityDescription’s class method:

NSManagedObjectContext *moc = kAppDelegate.managedObjectContext;

NSManagedObject *newObject =
[NSEntityDescription insertNewObjectForEntityForName:@"MyEntity"
inManagedObjectContext:moc];

After the new instance is available, the attributes can be updated. When complete, the managed object context needs to be saved to make the object persistent.

NSError *mocSaveError = nil;

if (![moc save:&mocSaveError])
{
NSLog(@"Save did not complete successfully. Error: %@",
[mocSaveError localizedDescription]);
}

The managed object context can be rolled back to throw away unwanted changes. This means that all the changes in the managed object context will be undone, will not be saved, and will not affect any other objects or managed object contexts.

if ([moc hasChanges])
{
[moc rollback];
NSLog(@"Rolled back changes.");
}

Fetching and Sorting Objects

To work with existing managed objects, they need to be fetched from the managed object context. There are two methods to fetch managed objects: directly using the objectID, or by constructing a fetch request.

Core Data gives each managed object a unique ID, which is an instance of NSManagedObjectID, called objectID. If that objectID has been stored for an object, it can be used to fetch the object:

NSManagedObject *myObject = [kAppDelegate.managedObjectContext
objectWithID:myObjectID];

The objectWithID method will return nil if an object with myObjectID is not found, or will return one NSManagedObject if it is found.

To fetch managed objects by specifying attribute criteria, a fetch request (instance of NSFetchRequest) is needed. The fetch request needs to know what entity is being fetched, which can be specified with an NSEntityDescription:

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

NSEntityDescription *entity =
[NSEntityDescription entityForName:@"MyEntity"
inManagedObjectContext:moc];

[fetchRequest setEntity:entity];

A fetch request can optionally have sort descriptors (NSSortDescriptor) to define the sort order for the returned results. Create a sort descriptor for each attribute that the search results should be sorted by, specifying the attribute name and sorting direction (ascending or descending). The sort descriptors are then added to an array in the order in which they should be applied to the results.

NSSortDescriptor *sortDescriptor =
[[NSSortDescriptor alloc] initWithKey:@"sortAttributeName"
ascending:YES];

NSArray *sortDescriptors =
[NSArray arrayWithObject:sortDescriptor];

[fetchRequest setSortDescriptors:sortDescriptors];

A fetch request can optionally use a predicate (NSPredicate) to define criteria that the returned results must match. Predicates for fetch requests are described in more detail in Chapter 13, in the “Using Predicates” subsection of the “Displaying Your Managed Objects” section.

NSPredicate *predicate =
[NSPredicate predicateWithFormat:@"attribute == %@",@"value"];

[fetchRequest setPredicate:predicate];

A fetch request will return zero, one, or multiple result objects in an NSArray. In cases where you would prefer to have the results attributes instead of objects, the fetch request can be set up to return just the attribute values instead of managed objects; it can even be set up to return aggregate functions like sum and count.

Fetched Results Controller

A fetched results controller is a powerful way to tie a fetch request to a UITableView. The fetched results controller sets up a fetch request so that the results are returned in sections and rows, accessible by index paths, and then exposes methods that make it convenient to get the information needed to implement a table view. In addition, the fetched results controller can listen to changes in Core Data and update the table accordingly using delegate methods, and can even animate changes to the table view. For a detailed explanation of setting up a fetched results controller–backed table view, refer to the section “Introducing the Fetched Results Controller” in Chapter 13.

The Core Data Environment

Core Data is able to hide the mechanics of persistence so that app code is able to work directly with model objects with no worries about how they are stored or saved. To do this, Core Data utilizes four major classes to define the environment: NSManagedObjectModel,NSPersistentStoreCoordinator, NSPersistentStore, and NSManagedObjectContext. When a Core Data–enabled project is set up in Xcode, the template provides logic to set up instances of these four classes. Adding Core Data to a new project is a fairly quick exercise,because all that is needed is to set up a managed object model, do any customizations needed to the rest of the environment, and begin writing app logic to use it.

The pattern that the Core Data template uses is to set up properties on the app delegate that are lazy loaded. When you instantiate the Core Data environment, each piece of the environment will be instantiated when it is needed. To instantiate a managed object model, load it from the model file. After the managed object model is instantiated, you can attach it to the persistent store coordinator.

Persistent Store Coordinator

The persistent store coordinator (NSPersistentStoreCoordinator) is the intermediary between the actual files that object data is stored in and the object model that the app interacts with. An app typically does not interact with a persistent store coordinator beyond just instantiating it when setting up the Core Data environment. The persistent store coordinator can manage communication with more than one persistent store so that the details of how the data are stored is hidden. After the persistent store coordinator is set up, a persistent store needs to be added to it to be able to access data.

Persistent Store

The persistent store (NSPersistentStore) represents a file where data is stored. When setting up an app to use Core Data, all that is needed is to specify the name, location, and type of the persistent store. Core Data can use SQLite and binary storage file types on iOS, and can use XML in Mac OS X (Core Data can also use an in-memory store, which does not save any data in a file). Most iOS apps use the SQLite option, since Core Data can leverage query capabilities for great performance. An important item to note is that Core Data manages the SQLite file directly. Core Data cannot use an existing SQLite file; it must create the file and schema itself, which it does when it first attempts to use the file if it is not present.

After a persistent store has been added successfully to the persistent store coordinator, it is time to set up the managed object context and interact with model objects.

Managed Object Context

The managed object context (NSManagedObjectContext) is a working area for managed objects. To create a new object, delete an object, or query existing objects, the app interacts with the managed object context. In addition, the managed object context can manage related changes. For example, the app could insert a few objects, update some objects, delete an object, and then save all those changes together or even roll them back if they are not needed.

More than one managed object context can be used at the same time to separate or confine work. Imagine that an app needs to display a set of data while it is importing some new data from a Web service. In that case, one managed object context would be used in the main thread to query and display existing data. Another managed object context would then be used in a background thread to import the data from the Web service. When the app is done importing data, it can merge the two managed object contexts together and dispose of the background context. Core Data is powerful enough to handle cases in which the same object is updated in both contexts, and can merge the changes together. The managed object context will also send a notification when a merge has been completed so that the app knows it is time to update the user interface. The notification contains detailed information about what changed so that the app can be very precise about updating what is on the screen.

Summary

This chapter introduced Core Data concepts, including the Core Data managed object, the managed object model, and Xcode’s visual managed object model editor. It showed how to create and fetch managed objects and interact with them in your app. The chapter explained how the Core Data environment is established in an iOS app, and how the persistent store coordinator, persistent stores, managed object contexts, and managed objects all work together to give you robust access to your application data. These are all the basic concepts needed to use Core Data; if it looks as though Core Data is right for your app, you can get up and running quickly with Chapter 13.


Note

There are no exercises for this chapter, since it’s really just an intro and the actual instruction takes place in Chapter 13.