Troubleshooting Core Data

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

Merge errors

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

Assigning a managed object to a different store

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.

Fault cannot be fulfilled

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

Managed object invalidated

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.

Class is not key-value coding compliant

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:).

Entity class does not respond to invocations of custom methods

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.

Custom accessor methods are not invoked, key dependencies are not obeyed

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

Problems with Fetching

SQLite store does not work with sorting

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.

Problems with Saving

SQLite store takes a "long time" to save

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.

Cannot save documents because entity is null

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.

Exception generated in retainedDataForObjectID:withContext.

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.

Debugging Fetching

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

Managed Object Models

My application generates the message "+entityForName: could not locate an NSManagedObjectModel"

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. The reference to the context may be nil.

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 setting the reference to the context in a nib file, make sure the appropriate outlet or binding is set correctly.

  • 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 managed object model 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.

Bindings Integration

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.

Custom relationship set mutator methods are not invoked by an array controller

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

Cannot access contents of an object controller after a nib is loaded

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

Cannot create new objects with array controller

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.

A table view bound to an array controller doesn't display the contents of a relationship

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.

A new object is not added to the relationship of the object currently selected in a table view

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.

Table view or outline view contents not kept up-to-date when bound to an NSArrayController or NSTreeController object

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