Using Managed Objects

This document describes issues related to using and manipulating managed objects in your application.

Accessing and Modifying Properties

Core Data automatically generates efficient public and primitive get and set accessor methods for modeled properties (attributes and relationships) of managed object classes (see “Managed Object Accessor Methods”). When you access or modify properties of a managed object, you should use these methods directly.

Most relationships are inherently bidirectional. Any changes made to the relationships between objects should maintain the integrity of the object graph. Provided that you have correctly modeled a relationship in both directions and set the inverses, modifying one end of a relationship automatically updates the other end—see “Manipulating Relationships and Object Graph Integrity.”

Attributes and to-one relationships

You access attributes and to-one relationships of a managed object using standard accessor methods or using the Objective-C 2.0 dot syntax (see “Dot Syntax” in The Objective-C 2.0 Programming Language) as illustrated in the following code fragment:

NSString *firstName = [anEmployee firstName];
Employee *manager = anEmployee.manager;

Similarly, you can use either standard accessor methods or the dot syntax to modify attributes; for example:

newEmployee.firstName = @"Stig";
[newEmployee setManager:manager];

In the cases of both getters and setters, the dot syntax is exactly equivalent to standard method invocation. For example, the following two statements use identical code paths:

[[aDepartment manager] setSalary:[NSNumber numberWithInteger:100000]];
aDepartment.manager.salary = [NSNumber numberWithInteger:100000];

You can also use key-value coding (KVC) to get or set the value of a simple attribute as illustrated in the following code fragment. Using KVC, though, is considerably less efficient than using accessor methods, so you should only use KVC when necessary (for example when you are choosing the key or key path dynamically).

[newEmployee setValue:@"Stig" forKey:@"firstName"];
[aDepartment setValue:[NSNumber numberWithInteger:100000] forKeyPath:@"manager.salary"];

You must, however, change attribute values in a KVC-compliant fashion. For example, the following typically represents a programming error:

NSMutableString *mutableString = [NSMutableString stringWithString:@"Stig"];
[newEmployee setFirstName:mutableString];
[mutableString setString:@"Laura"];

For mutable values, you should either transfer ownership of the value to Core Data, or implement custom accessor methods to always perform a copy. The previous example may not represent an error if the class representing the Employee entity declared the firstName property (copy) (or implemented a custom setFirstName: method that copied the new value). In this case, after the invocation of setString: (in the third code line) the value of firstName would then still be “Stig” and not “Laura”.

There should typically be no reason to invoke the primitive accessor methods except within custom accessor methods (see “Managed Object Accessor Methods”).

To-many relationships

To access a to-many relationship (whether the destination of a one-to-many relationship or a many-to-many relationship), you use the standard get accessor method. A to-many relationship is represented by a set, as illustrated in the following code fragment:

NSSet *managersPeers = [managersManager directReports];
NSSet *departmentsEmployees = aDepartment.employees;

When you access the destination of a relationship, you may initially get a fault object (see “Faulting and Uniquing”)—the fault fires automatically if you make any changes to it. (There’s typically no need to know whether the relationship is a fault, however you can find out using NSManagedObject’s hasFaultForRelationshipNamed: method.)

You can in principle manipulate an entire to-many relationship in the same way you do a to-one relationship, using either a custom accessor method or (more likely) key-value coding, as in the following example.

NSSet *newEmployees = [NSSet setWithObjects:employee1, employee2, nil];
[aDepartment setEmployees:newEmployees];
 
NSSet *newDirectReports = [NSSet setWithObjects:employee3, employee4, nil];
manager.directReports = newDirectReports;

Typically, however, you do not want to set an entire relationship, instead you want to add or remove a single element at a time. To do this, you should use mutableSetValueForKey: or one of the automatically-generated relationship mutator methods (see “Dynamically-Generated Accessor Methods”):

NSMutableSet *employees = [aDepartment mutableSetValueForKey:@"employees"];
[employees addObject:newEmployee];
[employees removeObject:firedEmployee];
 
// or
[aDepartment addEmployeesObject:newEmployee];
[aDepartment removeEmployeesObject:firedEmployee];

It is important to understand the difference between the values returned by the dot accessor and by mutableSetValueForKey:. mutableSetValueForKey: returns a mutable proxy object. If you mutate its contents, it will emit the appropriate key-value observing (KVO) change notifications for the relationship. The dot accessor simply returns a set. If you manipulate the set as shown in this code fragment:

[aDepartment.employees addObject:newEmployee]; // do not do this!

then KVO change notifications are not emitted and the inverse relationship is not updated correctly.

Recall that the dot simply invokes the accessor method, so for the same reasons:

[[aDepartment employees] addObject:newEmployee]; // do not do this, either!

Saving Changes

Simply modifying a managed object does not cause the changes to be saved to a store. The managed object context acts as a scratchpad. You can create and register managed objects with it, make changes to the objects, and undo and redo changes as you wish. If you make changes to managed objects associated with a given context, those changes remain local to that context until you commit the changes by sending the context a save: message. At that point—provided that there are no validation errors—the changes are committed to the store. As a corollary, simply creating a managed object does not cause it to be saved to a persistent store, and deleting a managed object does not cause the record to be removed from the store—you must save the context to commit the change.

See also “Ensuring Data Is Up-to-Date.”

Managed Object IDs and URIs

An NSManagedObjectID object is a universal identifier for a managed object, and provides basis for uniquing in the Core Data Framework. A managed object ID uniquely identifies the same managed object both between managed object contexts in a single application, and in multiple applications (as in distributed systems). Like the primary key in the database, an identifier contains the information needed to exactly describe an object in a persistent store, although the detailed information is not exposed. The framework completely encapsulates the “external” information and presents a clean object oriented interface.

NSManagedObjectID *moID = [managedObject objectID];

There are two forms of an object ID. When a managed object is first created, Core Data assigns it a temporary ID; only if it is saved to a persistent store does Core Data assign a managed object a permanent ID. You can readily discover whether an ID is temporary:

BOOL isTemporary = [[managedObject objectID] isTemporaryID];

You can also transform an object ID into a URI representation:

NSURL *moURI = [[managedObject objectID] URIRepresentation];

Given a managed object ID or a URI, you can retrieve the corresponding managed object using managedObjectIDForURIRepresentation: or objectWithID:.

An advantage of the URI representation is that you can archive it—although in many cases you should not archive a temporary ID since this is obviously subject to change. You could, for example, store archived URIs in your application’s user defaults to save the last selected group of objects in a table view. You can also use URIs to support copy and paste operations (see “Copying and Copy and Paste”) and drag and drop operations (see “Drag and Drop”).

You can use object IDs to define “weak” relationships across persistent stores (where no hard join is possible). For example, for a weak to-many relationship you store as archived URIs the IDs of the objects at the destination of the relationship, and maintain the relationship as a transient attribute derived from the object IDs.

You can sometimes benefit from creating your own unique ID (UUID) property which can be defined and set for newly inserted objects. This allows you to efficiently locate specific objects using predicates (though before a save operation new objects can be found only in their original context).

Copying and Copy and Paste

It is difficult to solve the problem of copying, or supporting copy and paste, in a generic way for managed objects. You need to determine on a case-by-case basis what properties of a managed object you actually want to copy.

Copying Attributes

If you just want to copy a managed object’s attributes, then in many cases the best strategy may be in the copy operation to create a dictionary (property list) representation of a managed object, then in the paste operation to create a new managed object and populate it using the dictionary. You can use the managed object’s ID (described in “Managed Object IDs and URIs”) to support copy and paste. Note, however, that the technique needs to be adapted to allow for copying of new objects.

A new, unsaved, managed object has a temporary ID. If a user performs a copy operation and then a save operation, the managed object’s ID changes and the ID recorded in the copy will be invalid in a subsequent paste operation. To get around this, you use a “lazy write” (as described in “Copy and Paste”). In the copy operation, you declare your custom type but if the managed object’s ID is temporary you do not write the data—but you do keep a reference to the original managed object. In the pasteboard:provideDataForType: method you then write the current ID for the object.

As a further complication, it is possible that the ID is still temporary during the paste operation, yet you must still allow for the possibility of future paste operations after an intervening save operation. You must therefore re-declare the type on the pasteboard to set up lazy pasting again, otherwise the pasteboard will retain the temporary ID. You cannot invoke addTypes:owner: during pasteboard:provideDataForType:, so you must use a delayed perform—for example:

- (void)pasteboard:(NSPasteboard *)sender provideDataForType:(NSString *)type
{
    if ([type isEqualToString:MyMOIDType]) {
        // assume cachedManagedObject is object originally copied
        NSManagedObjectID *moID = [cachedManagedObject objectID];
        NSURL *moURI = [moID URIRepresentation];
        [sender setString:[moURI absoluteString] forType:MyMOIDType];
        if ([moID isTemporaryID]) {
            [self performSelector:@selector(clearMOIDInPasteboard:)
                    withObject:sender afterDelay:0];
        }
    }
    // implementation continues...
}
 
- (void)clearMOIDInPasteboard:(NSPasteboard *)pb
{
    [pb addTypes:[NSArray arrayWithObject:MyMOIDType] owner:self];
}

Copying Relationships

If you want to copy relationships you also need to consider the objects related to those first tier of related objects—if you are not careful, it is possible that you will copy the whole object graph (which may not be what you want!). If you want to copy a to-one relationship, you need to decide whether the copy of the destination should be a new object or a reference. If it is a reference, what should happen to the inverse relationship to the original object—should making a copy redefine relationships between other objects? You need to make similar decisions for to-many relationships.

Drag and Drop

You can perform drag and drop operations with managed objects—such as, for example, transferring an object from one relationship to another—using a URI representation, as described in “Managed Object IDs and URIs.”

NSURL *moURI = [[managedObject objectID] URIRepresentation];

You can put the URI on a dragging pasteboard, from which you can later retrieve it and recreate a reference to the original managed object using the persistent store coordinator, as illustrated in the following code sample.

NSURL *moURL = // get it from the pasteboard ...
NSManagedObjectID *moID = [[managedObjectContext persistentStoreCoordinator]
    managedObjectIDForURIRepresentation:moURL];
// assume moID non-nil...
NSManagedObject *mo = [managedObjectContext objectWithID:moID];

This assumes that drag and drop is "within a single persistence stack"—that is, that if there is more than one managed object context involved that they use a shared persistent store coordinator—or that the object(s) being dragged and dropped are in a store referenced by the persistent store coordinators.

If you want to copy-and-paste via drag-and-drop then you must put a suitable representation of the managed object onto the pasteboard, get the representation during the drop method, and initialize a new managed object using the representation (see “Copying and Copy and Paste”).

Validation

The Core Data framework provides a clean infrastructure for supporting validation, both through logic encapsulated in the object model and through custom code. In the managed object model, you can specify constraints on values that a property may have (for example, an Employee's salary cannot be negative, or that every employee must belong to a Department). There are two forms of custom validation methods—those that follow standard key-value coding conventions (see “Key-Value Validation”) to validate a value for a single attribute, and a special set (validateForInsert:, validateForUpdate:, and validateForDelete:) for validating the whole object at different stages of its life-cycle (insertion, update, and deletion). The latter may be particularly useful for validating combinations of values—for example, to ensure that an employee can be entered into a stock purchase plan only if their period of service exceeds a given length and their pay grade is at or above a certain level.

Model-based constraints are checked and validation methods are invoked automatically before changes are committed to the external store to prevent invalid data being saved. You can also invoke them programmatically whenever necessary. You validate individual values using validateValue:forKey:error:. The managed object compares the new value with the constraints specified in the model, and invokes any custom validation method (of the form validate<Key>:error:) you have implemented. Even if you implement custom validation methods, you should typically not call custom validation methods directly. This ensures that any constraints defined in the managed object model are applied.

Undo Management

The Core Data framework provides automatic support for undo and redo. Undo management even extends to transient properties (properties that are not saved to persistent store, but are specified in the managed object model).

Managed objects are associated with a managed object context. Each managed object context maintains an undo manager. The context uses key-value observing to keep track of modifications to its registered objects. You can make whatever changes you want to a managed object’s properties using normal accessor methods, key-value coding, or through any custom key-value-observing compliant methods you define for custom classes, and the context registers appropriate events with its undo manager.

To undo an operation, you simply send the context an undo message and to redo it send the context a redo message. You can also roll back all changes made since the last save operation using rollback (this also clears the undo stack) and reset a context to its base state using reset.

You also can use other standard undo manager functionality, such grouping undo events. Core Data, though, queues up the undo registrations and adds them in a batch (this allows the framework to coalesce changes, negate contradictory changes, and perform various other operations that work better with hindsight than immediacy). If you use methods other than beginUndoGrouping and endUndoGrouping, to ensure that any queued operations are properly flushed you must first therefore send the managed object context a processPendingChanges message.

For example, in some situations you want to alter—or, specifically, disable—undo behavior. This may be useful if you want to create a default set of objects when a new document is created (but want to ensure that the document is not shown as being dirty when it is displayed), or if you need to merge new state from another thread or process. In general, to perform operations without undo registration, you send an undo manager a disableUndoRegistration message, make the changes, and then send the undo manager an enableUndoRegistration message. Before each, you send the context a processPendingChanges message, as illustrated in the following code fragment:

NSManagedObjectContext *moc = ...;
[moc processPendingChanges];  // flush operations for which you want undos
[[moc undoManager] disableUndoRegistration];
// make changes for which undo operations are not to be recorded
[moc processPendingChanges];  // flush operations for which you do not want undos
[[moc undoManager] enableUndoRegistration];

Faults

Managed objects typically represent data held in a persistent store. In some situations a managed object may be a “fault”—an object whose property values have not yet been loaded from the external store. When you access persistent property values, a fault “fires” and its persistent data is retrieved automatically from the store. In some circumstances you may explicitly turn a managed object into a fault (typically to ensure that its values are up to date, using NSManagedObjectContext's refreshObject:mergeChanges:). More commonly you encounter faults when traversing relationships.

When you fetch a managed object, Core Data does not automatically fetch data for other objects to which it has relationships (see “Faulting Limits the Size of the Object Graph”). Initially, an object's relationships are represented by faults (unless the destination object has already been fetched—see “Uniquing Ensures a Single Managed Object per Record per Context”). If, however, you access the relationship's destination object or objects, their data are retrieved automatically for you. For example, suppose you fetch a single Employee object from a persistent store when an application first launches, then (assuming these exist in the persistent store) its manager and department relationships are represented by faults. You can nevertheless ask for the employee’s manager’s last name as shown in the following code example:

NSString *managersName =
        [[anEmployee valueForKey:@"manager"] valueForKey:@"lastName];

or more easily using key paths:

NSString *managersName =
        [anEmployee valueForKeyPath:@"manager.lastName"];

In this case, the data for destination Employee object (the manager) is retrieved for you automatically.

There is a subtle but important point here. Notice that, in order to traverse a relationship—in this example to find an employee’s manager—you do not have to explicitly fetch the related objects (that is, you do not create and execute a fetch request). You simply use key-value coding (or if you have implemented them, accessor methods) to retrieve the destination object (or objects) and they are created for you automatically by Core Data. For example, you could ask for an employee’s manager’s manager’s department’s name like this:

NSString *departmentName = [anEmployee valueForKeyPath:@"manager.manager.department.name"];

(This assumes, of course, that the employee is at least two levels deep in the management hierarchy.) You can also use collection operator methods. You could find the salary overhead of an employee's department like this:

NSNumber *salaryOverhead = [anEmployee valueForKeyPath:@"department.employees.@sum.salary"];

In many cases, your initial fetch retrieves a starting node in the object graph and thereafter you do not execute fetch requests, you simply follow relationships.

Ensuring Data Is Up-to-Date

If two applications are using the same data store, or a single application has multiple persistence stacks, it is possible for managed objects in one managed object context or persistent object store to get out of sync with the contents of the repository. If this occurs, you need to “refresh” the data in the managed objects, and in particular the persistent object store (the snapshots) to ensure that the data values are current.

Refreshing an object

Managed objects that have been realized (their property values have been populated from the persistent store) as well as pending updated, inserted, or deleted objects, are never changed by a fetch operation without developer intervention. For example, consider a scenario in which you fetch some objects and modify them in one editing context; meanwhile in another editing context you edit the same data and commit the changes. If in the first editing context you then execute a new fetch which returns the same objects, you do not see the newly-committed data values—you see the existing objects in their current in-memory state.

To refresh a managed object's property values, you use the managed object context method refreshObject:mergeChanges:. If the mergeChanges flag is YES, the method merges the object's property values with those of the object available in the persistent store coordinator; if the flag is NO, the method simply turns an object back into a fault without merging (which also causes strong references to other related managed objects to be broken, so you can use this method to trim the portion of your object graph you want to hold in memory).

An object’s staleness interval is the time that has to pass until the store re-fetches the snapshot. This therefore only affects firing faults—moreover it is only relevant for SQLite stores (the other stores never re-fetch because the entire data set is kept in memory).

Merging changes with transient properties

If you use refreshObject:mergeChanges: with the mergeChanges flag YES, then any transient properties are restored to their pre-refresh value after awakeFromFetch is invoked. This means that, if you have a transient property with a value that depends on a property that is refreshed, the transient value may become out of sync.

Consider an application in which you have a Person entity with attributes firstName and lastName, and a cached transient derived property, fullName (in practice it might be unlikely that a fullName attribute would be cached, but the example is easy to understand). Suppose also that fullName is calculated and cached in a custom awakeFromFetch method.

A Person, currently named "Sarit Smith" in the persistent store, is edited in two managed object contexts:

  • In context one, the corresponding instance's firstName is changed to "Fiona" (which causes the cached fullName to be updated to "Fiona Smith") and the context saved.

    In the persistent store, the person is now “Fiona Smith”.

  • In context two, corresponding instance's lastName is changed to "Jones", which causes the cached fullName to be updated to "Sarit Jones".

    The object is then refreshed with the mergeChanges flag YES. The refresh fetches “Fiona Smith” from the store.

    • firstName was not changed prior to the refresh; the refresh causes it to be updated to the new value from the persistent store, so it is now "Fiona".

    • lastName was changed prior to the refresh; so, after the refresh, it is set back to its modified value—"Jones".

    • The transient value, fullName, was also changed prior to the refresh. After the refresh, its value is restored to "Sarit Jones" (to be correct, it should be "Fiona Jones").

The example shows that, because pre-refresh values are applied after awakeFromFetch, you cannot use awakeFromFetch to ensure that a transient value is properly updated following a refresh (or if you do, the value will subsequently be overwritten). In these circumstances, the best solution is to use an additional instance variable to note that a refresh has occurred and that the transient value should be recalculated. For example, in the Person class you could declare an instance variable fullNameIsValid of type BOOL and implement the didTurnIntoFault method to set the value to NO. You then implement a custom accessor for the fullName attribute that checks the value of fullNameIsValid—if it is NO, then the value is recalculated.