This article outlines some of the common issues encountered in applications that use Core Data and provides clues as to correcting the problem.
When troubleshooting Core Data-based applications, it is important to consider that Core Data provides functionality that builds on top of functionality provided by other parts of Cocoa. When attempting to diagnose a problem with an application that uses Core Data, you should take care to distinguish between issues that are specific to Core Data and those that arise because of an error with another framework or to an implementation or architectural patten. Poor performance, for example, may not be due to Core Data per se, but instead are due to a failure to observe standard Cocoa techniques of memory management or resource conservation; or if a user interface does not update properly, this may be due to an error in how you have configured Cocoa bindings.
Object Life-Cycle Problems
Problems with Fetching
Problems with Saving
Debugging Fetching
Managed Object Models
Bindings Integration
Problem: You see the error message, "Could not merge changes"
.
Cause: Two different managed object contexts tried to change the same data. This is also known as an optimistic locking failure.
Remedy: Either set a merge policy on the context, or manually (programmatically) resolve the failure. You can retrieve the currently committed values for an object using committedValuesForKeys:
, and you can re-fault the object (so that when it is next accessed its data values are retrieved from its persistent store) using refreshObject:mergeChanges:
.
Problem: You see an exception that looks similar to this example.
<NSInvalidArgumentException> [<MyMO 0x3036b0>_assignObject:toPersistentStore:]: |
Can’t reassign an object to a different store once it has been saved. |
Cause: The object you are trying to assign to a store has already been assigned and saved to a different store.
Remedy: To move an object from one store to another, you must create a new instance, copy the information from the old object, save it to the appropriate store, and then delete the old instance.
Problem: You see the error message, "Core Data could not fulfill a fault"
.
Cause: The corresponding object's underlying data has been deleted from the persistent store.
Remedy: You should discard this object.
This problem can occur in at least two situations:
First:
Start with a retained reference to a managed object.
Delete the managed object via the managed object context.
Save changes on the object context.
At this point, the deleted object has been turned into a fault. It isn't destroyed because doing so would violate the rules of memory management.
Try to retrieve an attribute or relationship from the previously retained reference.
Core Data will try to fault the faulted managed object but will fail to do so because the object has been deleted from the store. That is, there is no longer an object with the same global ID in the store.
Second:
Delete an object from a managed object context.
Fail to break all relationships from other objects to that object.
Save changes.
At this point, if you try to fire the relationship from some other object to that object, it may fail (this depends on the details of the configuration of the relationship as that affects how the relationship is stored).
The delete rules for relationships affect relationships only from the source object to other objects (including inverses). Without potentially fetching large numbers of objects, possibly without reason, there is no way for Core Data to efficiently clean up the relationships to the object.
Keep in mind that a Core Data object graph is directional. That is, a relationship has a source and a destination. Following a source to a destination does not necessarily mean that there is an inverse relationship. So, in that sense, you need to ensure that you are properly maintaining the object graph across deletes.
In practice, a well-designed object graph does not require much manual post-deletion clean up. If you consider that most object graphs have "entry points" that in effect act as a root node for navigating the graph and that most insertion and deletion events are rooted at those nodes just like fetches, then delete rules take care of most of the work for you. Similarly, since smart groups and other "casual" relationships are generally best implemented with fetched properties, various ancillary collections of entry points into the object graph generally do not need to be maintained across deletes because fetched relationships have no notion of permanence when it comes to objects found via the fetched relationship.
Problem: You see an exception that looks similar to this example:
<NSObjectInaccessibleException> [<MyMO 0x3036b0>_assignObject:toPersistentStore:]: |
The NSManagedObject with ID:#### has been invalidated. |
Cause: Either you have removed the store for the fault you are attempting to fire, or the managed object's context has been sent a reset
message.
Remedy: You should discard this object. If you add the store again, you can try to fetch the object again.
Problem: You see an exception that looks similar to the following example.
<NSUnknownKeyException> [<MyMO 0x3036b0> valueForUndefinedKey:]: |
this class is not key value coding-compliant for the key randomKey. |
Cause: Either you used an incorrect key, or you initialized your managed object with init
instead of initWithEntity:inManagedObjectContext:
.
Remedy: Use a valid key (check the spelling and case carefully—also review the rules for key-value coding compliance in Key-Value Coding Programming Guide), or ensure that you use the designated initializer for NSManagedObject
(see initWithEntity:insertIntoManagedObjectContext:
).
Problem: You define an entity that uses a custom subclass of NSManagedObject
, then in code you create an instance of the entity and invoke a custom method, as illustrated in this code fragment:
NSManagedObject *entityInstance = |
[NSEntityDescription insertNewObjectForEntityForName:@"MyEntity" |
inManagedObjectContext:managedObjectContext]; |
[entityInstance setAttribute: newValue]; |
You get a runtime error like this:
"2005-05-05 15:44:51.233 MyApp[1234] *** |
-[NSManagedObject setNameOfEntity:]: selector not recognized [self = 0x30e340] |
Cause: In the model, you may have misspelled the name of the custom class for the entity.
Remedy: Ensure that the spelling of name of the custom class in the model matches the spelling of the custom class you implement.
Problem: You define a custom subclass of NSManagedObject
for a particular entity and implement custom accessors methods (and perhaps dependent keys). At runtime, the accessor methods are not called and the dependent key is not updated.
Cause: In the model, you did not specify the custom class for the entity.
Remedy: Ensure that the model specifies of name of the custom class for the entity (that is, that it is not NSManagedObject
).
Problem: You create a sort descriptor that uses a comparison method defined by NSString
, such as the following:
NSSortDescriptor *mySortDescriptor = [[NSSortDescriptor alloc] |
initWithKey:@"lastName" ascending:YES |
selector:@selector(localizedCaseInsensitiveCompare:)]; |
You then either use this descriptor with a fetch request or as one of an array controller's sort descriptors. At runtime, you might see an error message that looks similar to the following:
NSRunLoop ignoring exception 'unsupported NSSortDescriptor selector: |
localizedCaseInsensitiveCompare:' that raised during posting of |
delayed perform with target 3e2e42 and selector 'invokeWithTarget:' |
Cause: Exactly how a fetch request is executed depends on the store—see “Fetching Managed Objects.”
Remedy: If you are executing the fetch directly, you should not use Cocoa-based sort operators—instead you should sort the returned array in memory. If you are using an array controller, you may need to subclass NSArrayController
so you can have it not pass the sort descriptors to the database and instead do the sorting after your data has been fetched.
Problem: You are using an SQLite store and notice that it takes longer to save to the SQLite store than it does to save the same data to an XML store.
Cause: This is probably expected behavior. The SQLite store ensures that all data is written correctly to disk—see “Configuring a SQLite Store’s Save Behavior.”
Remedy: First determine whether the time taken to save will be noticeable to the user. This is typically likely to be the case only if you configure your application to frequently save automatically—for example, after every edit that the user makes. First, consider changing the store’s save behavior (switch off full sync). Then consider saving data only after a set period (for example, every 15 seconds) instead of after every edit. If necessary, consider choosing a different store—for example, the binary store.
Problem: You have Core Data document-based application that is unable to save. When you try to save the document you get an exception:
Exception raised during posting of notification. Ignored. exception: Cannot perform operation since entity with name 'Wxyz' cannot be found |
Cause: This error is emitted by an instance of NSObjectController
(or one of its subclasses) that is set in Entity mode but can’t access the entity description in the managed object model associated with the entity name specified in Interface Builder. In short, you have a controller in entity mode with an invalid entity name.
Remedy: Select in turn each of your controllers in Interface Builder, and press Command-1 to show the inspector. For each controller, ensure you have a valid entity name in the "Entity Name" field at the top.
Problem: You add an object to a context. When you try to save the document you get an error that looks like this:
[date] My App[2529:4b03] cannot find data for a temporary oid: 0x60797a0 <<x-coredata:///MyClass/t8BB18D3A-0495-4BBE-840F-AF0D92E549FA195>x-coredata:///MyClass/t8BB18D3A-0495-4BBE-840F-AF0D92E549FA195> |
an exception in -[NSSQLCore retainedDataForObjectID:withContext:]
, and the backtrace looks like:
#1 0x9599a6ac in -[NSSQLCore retainedDataForObjectID:withContext:] |
#2 0x95990238 in -[NSPersistentStoreCoordinator(_NSInternalMethods) _conflictsWithRowCacheForObject:andStore:] |
#3 0x95990548 in -[NSPersistentStoreCoordinator(_NSInternalMethods) _checkRequestForStore:originalRequest:andOptimisticLocking:] |
#4 0x9594e8f0 in -[NSPersistentStoreCoordinator(_NSInternalMethods) executeRequest:withContext:] |
#5 0x959617ec in -[NSManagedObjectContext save:] |
The call to _conflictsWithRowCacheForObject:
is comparing the object you're trying to save with its last cached version from the database. Basically, it's checking to see if any other code (thread, process, or just a different managed object context) changed this object out from underneath you.
Core Data does not do this check on newly inserted objects because they could not have existed in any other scope. They haven't been written to the database yet.
Cause: You may have forced a newly inserted object to "lose" its inserted status and then changed or deleted it. This could happen if you passed a temporary object ID to objectWithID:
. You may have passed an inserted object to another managed object context.
Remedy: There are a number of possible remedies, depending on what was the root cause:
Do not pass an inserted (not yet saved) object to another context. Only objects that have been saved can be passed between contexts.
Do not invoke refreshObject:
on a newly-inserted object.
Do not make a relationship to an object that you never insert into the context.
Ensure that you use the designated initializer for instances of NSManagedObject
.
Before you save (frame #6 in the stack trace), the context’s updatedObjects
and deletedObjects
sets should only have members whose object ID returns NO
from isTemporaryID
.
With Mac OS X version 10.4.3 and later, you can use the user default com.apple.CoreData.SQLDebug
to log to stderr
the actual SQL sent to SQLite. (Note that user default names are case sensitive.) For example, you can pass the following as an argument to the application:
-com.apple.CoreData.SQLDebug 1 |
Higher levels of debug numbers produce more information, although using higher numbers is likely to be of diminishing utility.
The information the output provides can be useful when debugging performance problems—in particular it may tell you when Core Data is performing a large number of small fetches (such as when firing faults individually). Like file I/O, executing many small fetches is expensive compared to executing a single large fetch. For examples of how to correct this situation, see “Faulting Behavior.”
Important: Using this information for reverse engineering to facilitate direct access to the SQLite file is not supported. It is exclusively a debugging tool.
As this is for debugging, the exact format of the logging is subject to change without notice. You should not, for example, pipe the output into an analysis tool with the expectation that it will work on all OS versions.
Problem: The error states clearly the issue—the entity description cannot find a managed object model from which to access the entity information.
Cause: The model may not be included in your application resources. You may be trying to access the model before it has been loaded.
Remedy: Be sure that the model is included in your application resources and that the corresponding "project target" option in Xcode is selected.
The class method you invoked requires an entity name and context, and it is through the context that the entity gets the model. Basically, it looks like:
context ---> coordinator ---> model
In general, when working with Core Data and you have problems like these, you should ensure:
That the managed object context is not nil
If you are managing your own Core Data stack, that the managed object context has an associated coordinator (setPersistentStoreCoordinator:
after allocating)
That the persistent store coordinator has a valid model
If you are using NSPersistentDocument
, then the "magic" for getting the managed object model is that it is instantiated using the mergedModelFromBundles:
method when the document is initialized.
The documentation also gives you enough information on how to debug and hooks for debugging: there are a handful of methods listed in the "Getting and setting the persistence objects" section of the API reference for NSPersistentDocument
for either modifying or inspecting the Core Data objects your document is working with. Simply overriding the implementations, calling super, and inspecting the returned values would give you more information about what may (or may not) be occurring.
Many problems relating to bindings are not specific to Core Data, and are discussed in Troubleshooting Cocoa Bindings. This section describes some additional problems that could be caused by the interaction of Core Data and bindings.
Problem: You have implemented set mutator methods for a relationship as described in “Custom To-Many Relationship Accessor Methods,” and have bound the contentSet
binding of an NSArrayController
instance to a relationship (as illustrated by the Employees array controller in NSPersistentDocument Core Data Tutorial), but the set mutator methods are not invoked when you add objects to and remove objects from the array controller.
Cause: This is a bug.
Remedy: You can work around this by adding self
to the contentSet
binding's key path. For example, instead of binding to [Department Object Controller].selection.employees
, you would bind to [Department Object Controller].selection.self.employees
.
Problem: You want to perform an operation with the contents of an object controller (an instance of NSObjectController
, NSArrayController
, or NSTreeController
) after a nib file has been loaded, but the controller's content is nil
.
Cause: The controller's fetch is executed as a delayed operation performed after its managed object context is set (by nib loading)—the fetch therefore happens after awakeFromNib
and windowControllerDidLoadNib:
.
Remedy: You can execute the fetch “manually” with fetchWithRequest:merge:error:
—see “Core Data and Cocoa Bindings.”
Problem: You cannot create new objects using an NSArrayController
. For example, when you click the button assigned to the add:
action, you get an error similar to the following:
2005-05-05 12:00:)).000 MyApp[1234] *** NSRunLoop |
ignoring exception 'Failed to create new object' that raised |
during posting of delayed perform with target 123456 |
and selector 'invokeWithTarget:' |
Cause: In your managed object model, you may have specified a custom class for the entity, but you have not implemented the class.
Remedy: Implement the custom class, or specify that the entity is represented by NSManagedObject
.
Problem: You have a table view bound to an array controller that you want to display the contents of a relationship, but nothing is displayed and you get an error similar to the following:
2005-05-27 14:13:39.077 MyApp[1234] *** NSRunLoop ignoring exception |
'Cannot create NSArray from object <_NSFaultingMutableSet: 0x3818f0> () |
of class _NSFaultingMutableSet - consider using contentSet |
binding instead of contentArray binding' that raised during posting of |
delayed perform with target 385350 and selector 'invokeWithTarget:' |
Cause: You bound the controller's contentArray
binding to a relationship. Relationships are represented by sets.
Remedy: Bind the controller's contentSet
binding to the relationship.
Problem: You have a table view that displays a collection of instances of an entity. The entity has a relationship to another entity, instances of which are displayed in a second table view. Each table view is managed by an array controller. When you add new instances of the second entity, they are not added to the relationship of the currently-selected instance of the first.
Cause: The two array controllers are not related. There is nothing to tell the second array controller about the first.
Remedy: Bind the second array controller's contentSet
binding to the key path that specifies the relationship of the selection in the first array controller. For example, if the first array controller manages the Department entity, and the second the Employee entity, then the contentSet
binding of the second array controller should be [Department Controller].selection.employees
.
Problem: You have a table view or outline view that displays a collection of instances of an entity. As new instances of the entity are added and removed, the table view is not kept in sync.
Cause: If the controller's content is an array that you manage yourself, then it is possible you are not modifying the array in a way that is key-value observing compliant.
If the controller's content is fetched automatically, then you have probably not set the controller to "Automatically prepare content."
Alternatively, the controller may not be properly configured.
Remedy: If the controller's content is a collection that you manage yourself, then ensure you modify the collection in a way that is key-value observing compliant—see Troubleshooting Cocoa Bindings.
If the controller's content is fetched automatically, set the "Automatically prepares content" switch for the controller in the Attributes inspector in Interface Builder (see also automaticallyPreparesContent
). Doing so means that the controller tracks inserts into and deletions from its managed object context for its entity.
If neither of these is a factor, check to see that the controller is properly configured (for example, that you have set the entity correctly).
© 2004, 2009 Apple Inc. All Rights Reserved. (Last updated: 2009-03-04)