< Previous PageNext Page > Hide TOC

Mac OS X v10.4: Using Managed Objects

Many enhancements were introduced for Core Data on Mac OS X v10.5. For the benefit of developers who need to develop for earlier releases, this article describes issues related to using and manipulating managed objects on Mac OS X v10.4.

Contents:

Accessing and Modifying Properties
Saving changes
Managed Object IDs and URIs
Copying and Copy and Paste
Drag and Drop
Validation
Undo Management
Faults
Ensuring Data Is Up-to-Date


Accessing and Modifying Properties

To access or modify properties of a managed object, by default you use key-value coding, using the name of a property (as defined in the managed object model) as a key. This general principle applies both to attributes and relationships. There are some special considerations for to-many relationships.

Attributes

You can access attributes of a managed object using key-value coding, as illustrated in the following code fragment.

NSString *firstName = [newEmployee valueForKey:@"firstName"];
NSNumber *salary = [newEmployee valueForKey:@"salary"];

If you have defined a custom class for a given entity and implemented your own accessor methods, you can call those directly as illustrated in the following code fragment—note that in the second case the salary is represented by a float value (see “Managed Object Accessor Methods” for more details).

NSString *firstName = [newEmployee firstName];
float salary = [newEmployee salary];

The following code fragment illustrates how you can use key-value coding to change the value of a simple attribute.

[newEmployee setValue:@"Stig" forKey:@"firstName"];
[newEmployee setValue:[NSNumber numberWithFloat:1000.0] forKey:@"salary"];

If you have defined a custom class for a given entity and implemented your own accessor methods (see “Managed Object Accessor Methods” for implementation details), again you can call those directly as illustrated in the following code fragment.

[newEmployee setFirstName:@"Stig"];
[newEmployee setSalary:1000.0];

Key-value coding will, of course, also work if you’ve specified a custom class and custom accessors.

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

NSMutableString *mString = [NSMutableString stringWithString:@"Stag"];
[newEmployee setValue:mString forKey:@"firstName"];
[mString setString:@"Stig"];

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 would not represent an error if the class representing the Employee entity implemented a custom setFirstName: accessor method that copied the new value. It is important to note, of course, that after the invocation of setString: (in the third code line) the value of firstName would still be “Stag” and not “Stig”.

There should typically be no reason to invoke the primitive KVC set method (setPrimitiveValue:forKey:) or any custom primitive setters (see “Managed Object Accessor Methods”) except within accessor methods for derived properties.

Relationships

You access and modify a to-one relationship using key-value coding or a custom accessor method, just as you would an attribute—for example:

[newEmployee setValue:anotherEmployee forKey:@"manager"];
NSManagedObject *managersManager = [anotherEmployee manager];

To access a to-many relationship (whether the destination of a one-to-many relationship or a many-to-many relationship), you typically use key-value coding. A to-many relationship is represented by a set, as illustrated in the following code fragment:

NSSet *managersPeers = [managersManager valueForKey:@"directReports"];

Note that 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.

You can 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.

[aDepartment setValue:setOfEmployees forKey:@"employees"];

Typically, however, you do not want to set an entire relationship, instead you want to add or remove a single element at a time. In this case, you use mutableSetValueForKey: which returns a proxy object that both mutates the relationship and sends appropriate key-value observing notifications for you.

NSMutableSet *employees = [aDepartment mutableSetValueForKey:@"employees"];
[employees addObject:aNewEmployee];
[employees removeObject:aFiredEmployee];

There should typically be little reason to implement your own collection accessor methods (such as add<Key>Object: and remove<Key>Object:) for to-many relationships, however if you do so you may call these directly.

If you do implement custom accessors, you must take care to implement an add<Key>Object:/remove<Key>Object: pair, an add<Key>:/remove<Key>: pair, or both pairs and ensure that they send the relevant key-value observing notifications—see “Managed Object Accessor Methods” for implementation details.

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.”

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 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 persistent store.

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];

It is important to note that 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 note that 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. For an example, see NSPersistentDocument Core Data Tutorial—see also Copying in Model Object Implementation Guide. 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 Implementing 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];

Note that 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.

For more about implementing validation methods, see Model Object Validation.

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.

In some situations you want to alter—or, specifically, disable—undo behavior. This may be useful, for example, 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. Core Data, however, 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). To ensure that any queued operations are properly flushed, before you disable and enable undo registration you sent the managed object 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”). Initially, an object's relationships are represented by faults (unless the destination object has already been fetched—see “Uniquing”). 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, this 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 other related managed objects to be released, so you can use this method to trim the portion of your object graph you want to hold in memory).

Note that 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).

A Person, currently named "Alissa Eejaysing," is edited in two managed object contexts. In one context, the corresponding instance's lastName attribute is changed to "Wijesinghe" and the context saved. Afterwards, in the other context, the corresponding Person instance is modified such that the firstName is "Lasantha"—which in turn causes the fullName attribute to be updated to "Lasantha Eejaysing"—then refreshed with the mergeChanges flag YES. Since firstName was changed prior to the refresh, it remains "Lasantha". Since lastName was unchanged, the refresh causes it to be updated to the new value from the persistent store, so it is now "Wijesinghe." The transient value, however, is not updated. The value of fullName remains "Lasantha Eejaysing" (rather than the correct "Lasantha Wijesinghe").

Note that the pre-refresh values are applied after awakeFromFetch, so 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.



< Previous PageNext Page > Hide TOC


© 2004, 2009 Apple Inc. All Rights Reserved. (Last updated: 2009-03-04)


Did this document help you?
Yes: Tell us what works for you.
It’s good, but: Report typos, inaccuracies, and so forth.
It wasn’t helpful: Tell us what would have helped.