«Introduction to Core Data Programming Guide 11 Who Should Read This Document 11 Organization of This Document 11 See Also 13 Technology Overview 14 ...»
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” (page 71)).
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.
2012-09-19 | Copyright © 2004, 2012 Apple Inc. All Rights Reserved.
Using Managed Objects Undo Management 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 validateKey: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” (page 110)). 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” (page 112)). 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
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.
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").