Faulting and Uniquing
Faulting is a mechanism Core Data employs to reduce your application’s memory usage. A related feature called uniquing ensures that, in a given managed object context, you never have more than one managed object to represent a given record.
Faulting Limits the Size of the Object Graph
Faulting reduces the amount of memory your application consumes. A fault is a placeholder object that represents a managed object that has not yet been fully realized, or a collection object that represents a relationship:
A managed object fault is an instance of the appropriate class, but its persistent variables are not yet initialized.
A relationship fault is a subclass of the collection class that represents the relationship.
Faulting allows Core Data to put boundaries on the object graph. Because a fault is not realized, a managed object fault consumes less memory, and managed objects related to a fault are not required to be represented in memory at all.
To illustrate, consider an application that allows a user to fetch and edit details about a single employee. The employee has a relationship to a manager and to a department, and these objects in turn have other relationships. If you retrieve just a single Employee object from a persistent store, its manager, department, and reports relationships are initially represented by faults. Figure 1 shows an employee’s department relationship represented by a fault.
Although the fault is an instance of the Department class, it has not yet been realized—none of its persistent instance variables have yet been set. This means that not only does the department object consume less memory itself, but there’s no need to populate its employees relationship. If it were a requirement that the object graph be complete, then to edit a single attribute of a single employee, it would ultimately be necessary to create objects to represent the whole corporate structure.
Fault handling is transparent—you do not have to execute a fetch to realize a fault. If at some stage a persistent property of a fault object is accessed, then Core Data automatically retrieves the data for the object and initializes the object (see NSManagedObject Class Reference for a list of methods that do not cause faults to fire). This process is commonly referred to as firing the fault. If you send the Department object a message to get, say, its name, then the fault fires—and in this situation Core Data executes a fetch for you to retrieve all the object's attributes.
Core Data automatically fires faults when necessary (when a persistent property of a fault is accessed). However, firing faults individually can be inefficient, and there are better strategies for getting data from the persistent store (see “Batch Faulting and Pre-fetching with the SQLite Store”). For more about how to efficiently deal with faults and relationships, see “Fetching Managed Objects.”
When a fault is fired, Core Data does not go back to the store if the data is available in its cache. With a cache hit, converting a fault into a realized managed object is very fast—it is basically the same as normal instantiation of a managed object. If the data is not available in the cache, Core Data automatically executes a fetch for the fault object; this results in a round trip to the persistent store to fetch the data, and again the data is cached in memory.
The corollary of this point is that whether an object is a fault is not the same as whether its data has been retrieved from the store. Whether or not an object is a fault simply means whether or not a given managed object has all its attributes populated and is ready to use. If you need to determine whether an object is a fault, you can send it an
isFault message without firing the fault. If
NO, then the data must be in memory. However, if
YES, it does not imply that the data is not in memory. The data may be in memory, or it may not, depending on many factors influencing caching.
Turning Objects into Faults
Turning a realized object into a fault can be useful in pruning the object graph (see “Reducing Memory Overhead”), as well as ensuring property values are current (see “Ensuring Data Is Up-to-Date”). Turning a managed object into a fault releases unnecessary memory, sets its in-memory property values to
nil, and breaks strong references to related objects.
You can turn a realized object into a fault with the
refreshObject:mergeChanges: method. If you pass
NO as the mergeChanges argument, you must be sure that there are no changes to that object’s relationships. If there are, and you then save the context, you will introduce referential integrity problems to the persistent store.
When an object turns into a fault, it is sent a
didTurnIntoFault message. You may implement a custom
didTurnIntoFault method to perform various “housekeeping” functions (see, for example, “Ensuring Data Is Up-to-Date”).
Faults and KVO Notifications
When Core Data turns an object into a fault, key-value observing (KVO) change notifications (see Key-Value Observing Programming Guide) are sent for the object’s properties. If you are observing properties of an object that is turned into a fault and the fault is subsequently realized, you receive change notifications for properties whose values have not in fact changed.
Although the values are not changing semantically from your perspective, the literal bytes in memory are changing as the object is materialized. The key-value observing mechanism requires Core Data to issue the notification whenever the values change as considered from the perspective of pointer comparison. KVO needs these notifications to track changes across key paths and dependent objects.
Uniquing Ensures a Single Managed Object per Record per Context
Core Data ensures that—in a given managed object context—an entry in a persistent store is associated with only one managed object. The technique is known as uniquing. Without uniquing, you might end up with a context maintaining more than one object to represent a given record.
For example, consider the situation illustrated in Figure 2; two employees have been fetched into a single managed object context. Each has a relationship to a department, but the department is currently represented by a fault.
It would appear that each employee has a separate department, and if you asked each employee for their department in turn—turning the faults into regular objects—you would have two separate Department objects in memory. However, if both employees belong to the same department (for example, "Marketing"), then Core Data ensures that (in a given managed object context) only one object representing the Marketing department is ever created. If both employees belong to the same department, their department relationships would both therefore reference the same fault, as illustrated in Figure 3.
If Core Data did not use uniquing, then if you fetched all the employees and asked each in turn for their department—thereby firing the corresponding faults—a new Department object would be created every time. This would result in a number of objects, each representing the same department, that could contain different and conflicting data. When the context was saved, it would be impossible to determine which is the correct data to commit to the store.
More generally, all the managed objects in a given context that refer to the Marketing Department object refer to the same instance—they have a single view of Marketing’s data—even if it is a fault.