Core Data Basics
This article describes the basic Core Data architecture, and the way you use the framework.
Basic Core Data Architecture
In most applications, you need a means to open a file containing an archive of objects, and a reference to at least one root object. You also need to be able to archive all the objects to a file and—if you want to support undo—to track changes to the objects. For example, in an employee management application, you need a means to open a file containing an archive of employee and department objects, and a reference to at least one root object—for example, the array of all employees—as illustrated in Figure 1. You also need to be able to archive to a file all the employees and all the departments.
You are responsible for writing the code that manages these tasks either in whole or in part. For example, on OS X desktop, the Cocoa document architecture provides an application structure and functionality that helps to reduce the burden, but you still have to write methods to support archiving and unarchiving of data, to keep track of the model objects, and to interact with an undo manager to support undo.
Using the Core Data framework, most of this functionality is provided for you automatically, primarily through an object known as a managed object context (or just “context”). The managed object context serves as your gateway to an underlying collection of framework objects—collectively known as the persistence stack—that mediate between the objects in your application and external data stores. At the bottom of the stack are persistent object stores, as illustrated in Figure 2.
Core Data is not restricted to document-based applications—indeed it is possible to create a Core Data–based utility with no user interface at all (see Core Data Utility Tutorial). The same principles apply in other applications.
Managed Objects and Contexts
You can think of a managed object context as an intelligent scratch pad. When you fetch objects from a persistent store, you bring temporary copies onto the scratch pad where they form an object graph (or a collection of object graphs). You can then modify those objects however you like. Unless you actually save those changes, however, the persistent store remains unaltered.
Model objects that tie into in the Core Data framework are known as managed objects. All managed objects must be registered with a managed object context. You add objects to the graph and remove objects from the graph using the context. The context tracks the changes you make, both to individual objects' attributes and to the relationships between objects. By tracking changes, the context is able to provide undo and redo support for you. It also ensures that if you change relationships between objects, the integrity of the object graph is maintained.
If you choose to save the changes you've made, the context ensures that your objects are in a valid state. If they are, then the changes are written to the persistent store (or stores) and new records added for objects you created and records removed for objects you deleted.
You may have more than one managed object context in your application. For every object in a persistent store there may be at most one corresponding managed object associated with a given context (for more details, see Faulting and Uniquing). To consider this from a different perspective, a given object in a persistent store may be edited in more than one context simultaneously. Each context, however, has its own managed object that corresponds to the source object, and each managed object may be edited independently. This can lead to inconsistencies during a save—Core Data provides a number of ways to deal with this (see, for example, Using Managed Objects).
To retrieve data using a managed object context, you create a fetch request. A fetch request is an object that specifies what data you want, for example, “all Employees,” or “all Employees in the Marketing department ordered by salary, highest to lowest.” A fetch request has three parts. Minimally it must specify the name of an entity (by implication, you can only fetch one type of entity at a time). It may also contain a predicate object that specifies conditions that objects must match and an array of sort descriptor objects that specifies the order in which the objects should appear, as illustrated in Figure 3.
You send a fetch request to a managed object context, which returns the objects that match your request (possibly none) from the data sources associated with its persistent stores. Since all managed objects must be registered with a managed object context, objects returned from a fetch are automatically registered with the context you used for fetching. Recall though that for every object in a persistent store there may be at most one corresponding managed object associated with a given context (see Faulting and Uniquing). If a context already contains a managed object for an object returned from a fetch, then the existing managed object is returned in the fetch results.
The framework tries to be as efficient as possible. Core Data is demand driven, so you don't create more objects than you actually need. The graph does not have to represent all the objects in the persistent store. Simply specifying a persistent store does not bring any data objects into the managed object context. When you fetch a subset of the objects from the persistent store, you only get the objects you asked for. If you follow a relationship to an object that hasn't been fetched, it is fetched automatically for you. If you stop using an object, by default it will be deallocated. (This is of course not the same as removing it from the graph.)
Persistent Store Coordinator
As noted earlier, the collection of framework objects that mediate between the objects in your application and external data stores is referred to collectively as the persistence stack. At the top of the stack are managed object contexts, at the bottom of the stack are persistent object stores. Between managed object contexts and persistent object stores there is a persistent store coordinator.
In effect, a persistent store coordinator defines a stack. The coordinator is designed to present a façade to the managed object contexts so that a group of persistent stores appears as a single aggregate store. A managed object context can then create an object graph based on the union of all the data stores the coordinator covers. A coordinator can only be associated with one managed object model. If you want to put different entities into different stores, you must partition your model entities by defining configurations within the managed object models (see Configurations).
Figure 4 shows an example where employees and departments are stored in one file, and customers and companies in another. When you fetch objects, they are automatically retrieved from the appropriate file, and when you save, they are archived to the appropriate file.
A given persistent object store is associated with a single file or other external data store and is ultimately responsible for mapping between data in that store and corresponding objects in a managed object context. Normally, the only interaction you have with a persistent object store is when you specify the location of a new external data store to be associated with your application (for example, when the user opens or saves a document). Most other interactions with the Core Data framework are through the managed object context.
Your application code—and in particular the application logic associated with managed objects—should not make any assumptions about the persistent store in which data may reside. Core Data provides native support for several file formats. You can choose which to use depending on the needs of your application. If at some stage you decide to choose a different file format, your application architecture remains unchanged. Moreover, if your application is suitably abstracted, then you will be able to take advantage of later enhancements to the framework without any additional effort. For example—even if the initial implementation is able to fetch records only from the local file system—if an application makes no assumptions about where it gets its data from, then if at some later stage support is added for a new type of remote persistent store, it should be able to use this new type with no code revisions.
You can create and configure the persistence stack programmatically. In many cases, however, you simply want to create a document-based application able to read and write files. The
NSPersistentDocument class is a subclass of
NSDocument that is designed to let you easily take advantage of the Core Data framework. By default, an
NSPersistentDocument instance creates its own ready-to-use persistence stack, including a managed object context and a single persistent object store. There is in this case a one-to-one mapping between a document and an external data store.
NSPersistentDocument class provides methods to access the document’s managed object context and provides implementations of the standard
NSDocument methods to read and write files that use the Core Data framework. By default you do not have to write any additional code to handle object persistence. A persistent document’s undo functionality is integrated with the managed object context.
Managed Objects and the Managed Object Model
In order both to manage the object graph and to support object persistence, Core Data needs a rich description of the objects it operates on. A managed object model is a schema that provides a description of the managed objects, or entities, used by your application, as illustrated in Figure 5. You typically create the managed object model graphically using Xcode's Data Model Design tool. (If you wish you can construct the model programmatically at runtime.)
The model is composed of a collection of entity description objects that each provide metadata about an entity, including the entity's name, the name of the class that represents it in your application (this does not have to be the same as its name), and its attributes and relationships. The attributes and relationships in turn are represented by attribute and relationship description objects, as illustrated in Figure 6.
Managed objects must be instances of either
NSManagedObject or of a subclass of
NSManagedObject is able to represent any entity. It uses a private internal store to maintain its properties and implements all the basic behavior required of a managed object. A managed object has a reference to the entity description for the entity of which it is an instance. It refers to the entity description to discover metadata about itself, including the name of the entity it represents and information about its attributes and relationships. You can also create subclasses of
NSManagedObject to implement additional behavior.