Enable saving of document NSManagedObjectContext immediately? - core-data

Starting from the standard Xcode Document-based Application w/ CoreData template on 10.7, I'm experiencing some frustrating behavior. I'm sure it's something simple that I'm overlooking.
Let's say in my NSPersistentDocument subclass, I have something like this, hooked up to a button in the window:
- (IBAction)doStuff:(id)sender
{
NSEntityDescription* ent = [[self.managedObjectModel entitiesByName] valueForKey: #"MyEntity"];
NSManagedObject* foo = [[[NSManagedObject alloc] initWithEntity: ent insertIntoManagedObjectContext: self.managedObjectContext] autorelease];
[self.managedObjectContext save: NULL];
}
If I create a new document and click that button, I'll get the following error: This NSPersistentStoreCoordinator has no persistent stores. It cannot perform a save operation. I get this. We haven't saved yet, there are no persistent stores. Makes sense.
Now let's say I split this out into two actions, hooked up to different buttons, like so:
- (IBAction)doStuff:(id)sender
{
NSEntityDescription* ent = [[self.managedObjectModel entitiesByName] valueForKey: #"MyEntity"];
NSManagedObject* foo = [[[NSManagedObject alloc] initWithEntity: ent insertIntoManagedObjectContext: self.managedObjectContext] autorelease];
}
- (IBAction)doOtherStuff:(id)sender
{
[self.managedObjectContext save: NULL];
}
If I create a new document and press the first button, then at some indeterminate time after pressing that button (dirtying the document), autosave will come along and autosave the document, which will create a store in the temp location. If I then press the second button, there are no complaints (since there's now a store.)
I need my document to be able to do managedObjectContext saves from the get-go. I'm kicking off some stuff on a background thread, and I need the background context's save operation (and notification) in order to merge the changes made by the background thread into the main thread's managedObjectContext.
I thought about trying to force an autosave, but the autosave process appears completely asynchronous, so I'd have to jump through hoops to disable any UI interaction that could cause a managedObjectContext save until the first autosave operation completes.
I also considered creating an in-memory store to bridge the gap between creating a new document and the first autosave, but then it's not clear to me how I would migrate that in memory store to the disk store and remove the memory store synchronously with the first autosave operation.
Anyone have any ideas on how I might handle this?

So I fooled around with this for a while, including trying #Aderstedt's suggestion. That approach didn't work, because faking a notification appears to merely tell the receiving context "hey, check with the persistent stores, I've updated them!", when in reality, I haven't, because there are none. I eventually found an approach that worked. Unfortunately it relies on Lion-only features, so I'm still looking for a way to do this that doesn't require Lion.
Background
I wanted to work with the NSPersistentDocument approach. Although I've not found this documented explicitly anywhere, I found several forum posts, and experienced a bunch of empirical evidence that you cannot call -[NSManagedObjectContext save:] on a context that belongs to an NSPersistentDocument. As mentioned in the question, if you call that before the document has ever been saved, it'll have no stores, so the save will fail. Even after the store exists, by saving the context directly (and not via the document save API) you are effectively changing the on-disk representation behind the NSPersistentDocument's back, and you will get the document popping sheets that say:
File has been modified by another application
In short, the NSPersistentDocument expects to control the saving action of the associated NSManagedObjectContext itself.
Also worth mentioning up front: the goal here was to make sure that the context used by the UI would trigger no (or at least minimal) I/O in order to remain responsive. The pattern I eventually settled on was to have 3 contexts. One context owned by the NSPersistentDocument, which would be responsible for doing file I/O in conjunction with the document. A second, effectively read-only, context for binding the UI to. (I realize that many folks want UI that mutates the model, so this may be less thrilling for them, but it wasn't a requirement for me.) And a third context for use on a background thread that asynchronously loads data from a web service, and hopefully pushes it into the other contexts so that it can be both saved on disk and presented in the UI without potentially blocking the UI on network I/O.
Lion-only Solution
The new parent/child NSManagedObjectContext feature in Lion's CoreData implementation is perfect for this. I replaced the NSPersistentDocument's NSManagedObjectContext with an new MOC of concurrency type NSPrivateQueueConcurrencyType. This will be the "root" context. Then I made the UI context with NSMainQueueConcurrencyType concurrency, and made it a child of the root context. Lastly I made the network-loading context a NSPrivateQueueConcurrencyType context which is a child of the UI context. The way this works is that we kick off a network load operation in the background, it updates the network context. When it's done, it saves the context. With parent/child relationships, saving a child context pushes the changes up into the parent context (the UI context) but does not save the parent context to the store. In my case, I also listen for the NSManagedObjectContextDidSaveNotification notification from the network context and then tell it's parent to save as well (which will push the changes from the UI context into the root/disk context, but will not save it to disk.)
At the end of this chain of events all the contexts are consistent, and we still haven't forced a real save of the underlying root context, and so we haven't run afoul of the NSPersistentDocument in its role of managing the on-disk representation.
One catch is that if you want to prevent saves of child contexts from generating undo (i.e. this was a network loading operation, there's nothing to undo) you have to disableUndoRegistration on each parent context as you propagate the changes up the chain.
Pre-Lion Efforts
I would really have liked to find a pre-Lion-compatible solution for this issue. I tried a few things before giving up. I first tried associating an in-memory store with the PSC on document init, so that I could do NSManagedObjectContext saves before the document saved, then migrate the in-memory store on the first save. That part worked great. But once an on-disk store existed, this approach was bogus because after it's saved to disk we have the same problem where any saving of MOCs connected to PSCs owned by an NSPersistentDocument have to be done by the document.
I also tried hacking up a mechanism to move changes from one context to another using the NSManagedObjectContextObjectsDidChangeNotification payload. Although I was able to get this to work (for some nominal definition of 'work'), I saw big problems looming on the horizon with this approach. Specifically, it's easy to migrate those changes once but what if it changes again before a save operation? Then I'd be stuck maintaining a long lived mapping of OIDs in the source context to OIDs in the destination context(s). This got ugly really fast. If anyone's interested, here's what I came up with:
#interface NSManagedObjectContext (MergeChangesFromObjectsDidChangeNotification)
- (void)mergeChangesFromObjectsDidChangeNotification: (NSNotification*)notification;
#end
#implementation NSManagedObjectContext (MergeChangesFromObjectsDidChangeNotification)
- (void)mergeChangesFromObjectsDidChangeNotification: (NSNotification*)notification
{
if (![NSManagedObjectContextObjectsDidChangeNotification isEqual: notification.name])
return;
if (notification.object == self)
return;
NSManagedObjectContext* sourceContext = (NSManagedObjectContext*)notification.object;
NSAssert(self.persistentStoreCoordinator == sourceContext.persistentStoreCoordinator, #"Can't merge changes between MOCs with different persistent store coordinators.");
[sourceContext lock];
// Create object in the local context to correspond to inserted objects...
NSMutableDictionary* foreignOIDsToLocalOIDs = [NSMutableDictionary dictionary];
for (NSManagedObject* foreignMO in [[notification userInfo] objectForKey: NSInsertedObjectsKey])
{
NSManagedObjectID* foreignOID = foreignMO.objectID;
NSManagedObject* localMO = [[[NSManagedObject alloc] initWithEntity: foreignMO.entity insertIntoManagedObjectContext: self] autorelease];
[foreignOIDsToLocalOIDs setObject: localMO.objectID forKey: foreignOID];
}
// Bring over all the attributes and relationships...
NSMutableSet* insertedOrUpdated = [NSMutableSet set];
[insertedOrUpdated unionSet: [[notification userInfo] objectForKey: NSInsertedObjectsKey]];
[insertedOrUpdated unionSet: [[notification userInfo] objectForKey: NSUpdatedObjectsKey]];
for (NSManagedObject* foreignMO in insertedOrUpdated)
{
NSManagedObjectID* foreignOID = foreignMO.objectID;
NSManagedObjectID* localOID = [foreignOIDsToLocalOIDs objectForKey: foreignOID];
localOID = localOID ? localOID : foreignOID;
NSManagedObject* localMO = [self objectWithID: localOID];
// Do the attributes.
[localMO setValuesForKeysWithDictionary: [foreignMO dictionaryWithValuesForKeys: [[foreignMO.entity attributesByName] allKeys]]];
// Do the relationships.
NSDictionary* rByName = foreignMO.entity.relationshipsByName;
for (NSString* key in [rByName allKeys])
{
NSRelationshipDescription* desc = [rByName objectForKey: key];
if (!desc.isToMany)
{
NSManagedObject* relatedForeignMO = [foreignMO valueForKey: key];
NSManagedObjectID* relatedForeignOID = relatedForeignMO.objectID;
NSManagedObjectID* relatedLocalOID = [foreignOIDsToLocalOIDs objectForKey: relatedForeignOID];
relatedLocalOID = relatedLocalOID ? relatedLocalOID : relatedForeignOID;
NSManagedObject* localRelatedMO = [self objectWithID: relatedLocalOID];
[localMO setValue: localRelatedMO forKey: key];
}
else
{
id collection = [foreignMO valueForKey: key];
id newCollection = [NSMutableSet set];
if ([collection isKindOfClass: [NSOrderedSet class]])
{
newCollection = [NSOrderedSet orderedSet];
}
for (NSManagedObject* relatedForeignMO in collection)
{
NSManagedObjectID* relatedForeignOID = relatedForeignMO.objectID;
NSManagedObjectID* relatedLocalOID = [foreignOIDsToLocalOIDs objectForKey: relatedForeignOID];
relatedLocalOID = relatedLocalOID ? relatedLocalOID : relatedForeignOID;
NSManagedObject* localRelatedMO = [self objectWithID: relatedLocalOID];
[newCollection addObject: localRelatedMO];
}
[localMO setValue: newCollection forKey: key];
}
}
}
// And delete any objects which pre-existed in my context.
for (NSManagedObject* foreignMO in [[notification userInfo] objectForKey: NSDeletedObjectsKey])
{
NSManagedObjectID* foreignOID = foreignMO.objectID;
NSManagedObject* localMO = [self existingObjectWithID: foreignOID error: NULL];
if (localMO)
{
[self deleteObject: localMO];
}
}
[sourceContext unlock];
}
#end
Conclusion
Between the improvements in concurrency management and this parent/child feature, I quickly lost interest pursuing a pre-Lion solution. I'm beginning to gather that the pre-Lion solution would effectively be "Don't use NSPersistentDocument." As best I can tell, all these pain points go away if I drop that requirement. Without that, you can save contexts and migrate stores whenever you want, but naturally you would have to do all that work yourself.

The problem here is that the default NSManagedObjectContext that is created by the NSPersistentDocument is of concurrency type NSConfinementConcurrencyType. CoreData does not allow to create a child context of a context with that type.
As a workaround, this is working for me. The NSManagedObjectContext is created by your NSPersistentDocument, so you can override that method:
- (NSManagedObjectContext *)managedObjectContext {
if (!_context) {
NSManagedObjectContext *_default = [super managedObjectContext];
_context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_context.persistentStoreCoordinator = _default.persistentStoreCoordinator;
}
return _context;
}
I had no idea where to get the persistentStoreCoordinator from, so I called the super implementation and get it there from. With this context you should be able to create child context from that you can use in background operations.

If you don't have a store then you can't save. It seems that you want to save the document in order to merge the changes made on a background thread; well, you can merge those changes manually. When the background thread completes, tell the main thread which objects have been updated / inserted and then make the same changes on the main thread.
If the changes are near arbitrary and thus tedious to duplicate, you can even construct your own NSManagedObjectContextDidSaveNotification on the background thread and then merge it using -[NSManagedObjectContext mergeChangesFromContextDidSaveNotification:] on the main thread.

I found a very nicely written-up solution along the lines of ipmcc's solution (also running the NSPersistentDocument's MOC in a background thread, and making it the parent context of the main thread's MOC) here.
It includes the full BSD-licensed code for a document app based on that (basically a Mac OS version of iOS's UIManagedDocument).

Related

Core Data: NSObjectID and NSTemporaryObjectID leaks

Before I send my app to the App Store I like to check it for memory leaks and other fishy stuff with instruments. There is one Core Data issue that I can't seem to solve, so I've decided to create a small test app to illustrate the problem.
What's the problem?
When I save an entity in a (child) NSManagedObjectContext it is propagated to its parent NSManagedObjectContext. During this process Core Data creates internal instances of _NSObjectID and NSTemporaryObjectID. For some reason these instances are left behind and the only way to get rid of them is to reset the parent NSManagedObjectContext.
My app is of course a lot more complex than this little test app and resetting the NSManagedObjectContext isn't an option for me.
Test app
The test app is a standard iOS app based on the single view template with the CoreData option checked. I've used objective-c to keep it similar to my production app.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Initialize the Core Data stack
self.persistentStoreCoordinator = [self persistentStoreCoordinator];
// Create the a private context
self.rootContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
self.rootContext.persistentStoreCoordinator = self.persistentStoreCoordinator;
// Create a child context
self.childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
self.childContext.parentContext = self.rootContext;
// Create a person
[self.childContext performBlockAndWait:^{
Person *person = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:self.childContext];
person.name = #"John Smith";
person.age = 30;
// Save the person
[self.childContext save:nil];
// Save the root context
[self.rootContext performBlockAndWait:^{
[self.rootContext save:nil];
}];
}];
return YES;
}
When you run the code above with instruments and the allocations instrument you can see that Core Data leaves some stuff behind.
You can find the full project here: https://github.com/Zyphrax/CoreDataLeak
Things I've tried
I've tried things like [context refreshObject:... mergeChanges:YES], adding #autoreleasepool and/or [context processPendingChanges] inside the blocks, it all doesn't help. The only way to get it clean is to do a [context reset] (sledgehammer approach).
It's hard to find other people reporting this problem.
This blog post seems similar:
http://finalize.com/2013/01/04/core-data-issues-with-memory-allocation/
I hope you guys can help me with this.
Here is what I see, which is very similar to yours...
However, I don't know that I would be concerned, unless you see lots of these, and they never go away. I assume the internals of Core Data (including the row cache has) some sort of object caching going on.
On the other hand, my Core Data usage has changed a bit over the past year or two.
Unless it is a very simple app, I almost never create new objects in a child context. I will fetch and modify them, but if I end up creating a new object, I make sure that is done in a sibling context.
However, if you modify your code slightly, by adding this line (with your appropriate error handling - it returns BOOL) before the initial save...
NSArray *inserted = self.childContext.insertedObjects.allObjects;
[self.childContext obtainPermanentIDsForObjects:inserted error:&error];
you should get something like this instruments report, which shows all objects created as being transient...
Thus, I don't necessarily think it is a permanent leak, because once I force the context to convert to a permanent ID, the objects go away. However, who knows how long they keep those object ID objects cached.
In general, when I create objects in a context that contains a hierarchy, I will always obtain permanent IDs first (for many reasons). However, as I said earlier, I usually create new objects in a context that is directly created to the persistent store (because I have had to deal with other issues related to hierarchies temporary object IDs, especially when using multiple non related contexts).

Alternate Causes for "'NSInternalInconsistencyException', reason: 'This NSPersistentStoreCoordinator has no persistent stores.'"

thanks for reading. We've recieved crash reports on our iOS app with the following error:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'This NSPersistentStoreCoordinator has no persistent stores. It cannot perform a save operation.'
This occurs when our main queue NSManagedObjectContext attempts to save:
BOOL saved = [managedObjectContext save:&error];
Which occurs in a block submitted via:
[managedObjectContext performBlockAndWait:saveBlock];
The internet (especially SO) is full of explanations for this error suggesting that we never had a persistent store cooridinater (that we failed to create it properly when we built the core data stack). However, it's difficult to see how we could get to this point in program execution without a persistent store coordinator. For kicks, i commented out the line where we set the persistent store and the app crashes almost immediately (without getting to our save method).
Does anyone know if there's an alternate cause for this exception or is it ALWAYS because there is no persistent store? Is there any way to lose a persistent store?
A little more color: we use parent/child NSManagedObjectContexts in order to load data in a background thread. The full save method looks like this:
-(void)saveWithManagedObjectContext:(NSManagedObjectContext*)managedObjectContext successHandler:(void (^)())successHandler failureHandler:(void (^)(NSManagedObjectContext* managedObjectContext, NSError*))failureHandler
{
void(^saveBlock)() = ^() {
NSError *error = nil;
BOOL saved = [managedObjectContext save:&error];
if (!saved) {
if (error != nil) {
NSLog(#"Unresolved error saving %#, %#", error, [error userInfo]);
}
failureHandler(managedObjectContext, error);
} else {
if (managedObjectContext.parentContext != nil) {
[self saveWithManagedObjectContext: managedObjectContext.parentContext successHandler:successHandler failureHandler:failureHandler];
} else {
successHandler();
}
}
};
BOOL isMainContext = managedObjectContext.parentContext == nil;
if (isMainContext) {
[managedObjectContext performBlockAndWait:saveBlock];
} else {
[managedObjectContext performBlock:saveBlock];
}
}
It can be called from a background thread which will call save: on the NSManagedObjectContext via a performBlock: and then rescursively call this method on the parent NSManagedObjectContext in order to save it. When the app crashes, it's always on the main thread which makes sense because the main queue context is the only one that needs a persistent store.
Many thanks for any help. I did cross post to devforums.apple.com, so my apologies if you've seen this twice.
There are basically two places to look.
First, check what happens when you first add the persistent store to the Core Data stack. That would be in the app delegate if you are using the Apple template, but typically somewhere in your code when the app is initialized there is a call to
addPersistentStoreWithType:configuration:URL:options:error:.
Second, as you mention background threads and because you are passing a managed object context to your method, you possibly have child contexts. Check that you have properly assigned either a valid parent context or the store coordinator to the child context when you create it.
This error is unfortunately ambiguous. I have seen it when using configurations and there is a typo in the configuration name or in one case where a configuration name was passed in when there were no configurations in the model.
So my first line of testing would be to look at your Core Data stack creation code. Can you add that to your question so we can take a look at it?
I corresponded offline with Tom Harrington who made the innocuous comment: "Persistent stores shouldn't go away unless you remove them (and I'm assuming you don't do that)." 'course i DO do that when i tear down the core data stack (when a user logs out.) When i tear down the stack, i call reset on my root managed object context (the only one i have a reference to) and then remove the persistent store. However, if there are pending changes in a child context, they'll propagate up to my now persistentstoreless root context causing the crash on save. Since there's a good reason why parent contexts don't keep track of their children, i don't want to keep track of them either. Instead i just confirm that there is either a parent context or a persistent store coordinator (with at least one store) before calling save.

Core Data NSManagedObject Insert and Save Methods

Is there an easier way to do something like the following in Core Data:
Entry *entry = [[Entry alloc] init];
entry.name = #"An Entry";
[entry save];
I realize you don't have to allocate an NSManagedObject, have to insert directly into the context like the following:
Entry *entry = [NSEntityDescription insertNewObjectForEntityForName:#"Entry"
inManagedObjectContext:[self managedObjectContext]];
But this is a lot of code. Also I would like to save by just messaging rather than have to save the entire context:
NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil)
{
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
Could I put these in an NSManagedObject abstract class and have my managed objects extend that abstract class? Basically, I'm just trying to encapsulate more in my models and write less code in my controllers. Any help appreciated.
You can define your own subclass of NSManagedObject, and set all of your entities to use it.
The subclass can have whatever initialiser/save patterns you define, so long as it calls the proper parent class's initialiser.
You could have a +entity method, which might link to a statically defined context (this will restrict you to a single managed object context of course, but that's not always bad as long as you can also call the more primitive initialisers when you need a second context).
You might even have a +entityWithName: method.
As for saving the context, once again you can always define a subclass and add a simple -save method which saves the context and throws an exception if the save fails. You may choose to do this with a category extending the NSManagedObject class, instead of a subclass.
Note it is impossible to save just the change you made to that one entry object. You can only save all changes to an entire managed object context. If you need to save a single record, then you need to create a temporary managed object context, make a single change in it, then save the temporary context, and then sync the temporary context change over to all other managed object context's that currently exist in the app (there is an API to do make this complicated process relatively easy).
I don't like the code you posted to save the context, for several reasons:
Don't define a managedObjectContext variable that just points to self.managedObjectContext. In almost all situations that's an extra line of code for no benefit. At best you're making your code hard to read, at worst you might be introducing bugs.
Why are you checking if it is nil? Usually you should design your code so that it cannot ever be nil. Do the nil check in the constructor of your object, and if it's nil that is a critical failure. Your whole app is completely useless for the user, and you should make it clear to the user that they can't use the app by doing something drastic, such as a crash (an error alert first would be nice, but I wouldn't bother. I'd just throw an exception).
Why are you doing a check for hasChanges? I cannot think of many situations where you would need to do this check. Perhaps your app is allowing a user to make many changes, and then saving them several minutes later? This is bad. The context should be changed milliseconds after a group of changes are made, or else you're risking data loss. Your app could crash, the phone could run out of battery, or the user might receive a phone call and your app is consuming enough RAM that the OS will terminate it instantly in order to present the "incoming call" screen. You shouldn't need to check for hasChanges because you always perform a save operation immediately after making some changes.
As I kind of mentioned before, if the save fails you should present an error to the user then throw an exception. Avoid using NSLog() in deployment code, it's really only useful for development and beta builds.
Check out
NSManagedObject+ActiveRecord.h
Inside Restkit : http://restkit.org/
It is based on :
https://github.com/magicalpanda/MagicalRecord
I am using in RestKit app, but you can adopt it quite easily.
Good luck

NSFetchedResultsController on main thread ignoring saves to Core Data from a different thread?

I have an NSFetchedResultsController on the main thread. Also from the main thread, I asynchronously send out a network request for JSON. When that JSON string returns, I start an NSOperation that inits a new (background) NSManagedObjectContext, parses the JSON string, creates/updates NSManagedObject's, and saves them in the context. The background context has the same persistentStore as the main context. With this, I have 2 questions:
I thought that any saves to the persistent store from any context (on any thread) would notify the main NSFetchedResultsController that there are changes, but so far it doesn't pick up any changes. Is there something I should do to notify the main thread's NSFetchedResultsController that there were external save's so that the tableView updates accordingly?
So, on the main thread, I subscribe to NSManagedObjectContextWillSaveNotification and correctly see when all contexts (including those existing entirely on a separate thread) perform a save operation. The apple docs say that the notification.userInfo should have a dictionary of 3 arrays, one array for each of the "updated, deleted, and inserted" model objects on the background thread. However, the userInfo is always nil for me. Any ideas what I'm doing wrong?
Subscribing to the NSManagedObjectContextWillSaveNotification in AppDelegate:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(managedObjectContextDidSave:)
name:NSManagedObjectContextWillSaveNotification
object:nil];
And the method for when contexts are saved in AppDelegate:
- (void)managedObjectContextDidSave:(NSNotification *)notification {
DLog(#"notification: %#", notification); //not nil
DLog(#"notification user info: %#", notification.userInfo); // always nil... why??
NSManagedObjectContext *theContext = notification.object;
if(theContext != context) {
DLog(#"---- SAVED ON ANOTHER CONTEXT");
// should I notify NSFetchedResultsController that there were context saves on background threads?
// how can I merge contexts if userInfo is nil?
}
}
I'd also like to know the best practices in dealing with multiple threads (with separate NSManagedObjectContexts) and Core Data.
You observe the wrong notification: the name of the notification you need to observe is NSManagedObjectContextDidSaveNotification (not NSManagedObjectContextWillSaveNotification).

"Core Data could not fulfill a fault.." error

I am developing an application in cocoa. I am facing a critical problem.
I am deleting entries of an object named "Directory" in Core Data using the following code:
NSEnumerator *tempDirectories = [[folderArrayController arrangedObjects] objectEnumerator];
id tempDirectory;
while (tempDirectory = [tempDirectories nextObject]){
[managedObjectContext deleteObject:tempDirectory];
}
But sometimes an exception like "Core Data could not fulfill a fault.." occurs while trying to save after deletion. I am using the code [managedObjectContext save];
I am new in Core Data... Looking forward to a solution.
This is an old question, I have struggled resolving this for some time now. So, thought it would be best to document it.
As Weichsel above mentioned, the Apple documentation rightly points out reason for this exception. But it is a hectic job to identify the module due to which the NSManagedObject subclass' object is being retained (if 1st cited reason in the documentation is the root cause of the problem).
So, I started out by identifying the parts of my code which was retaining the NSManagedObject, instead I retained the NSManagedObjectID and create the managed object out of it whenever needed. The discussion in similar lines can be found in Restkit documentation:
https://github.com/RestKit/RestKit/commit/170060549f44ee5a822ac3e93668dad3b396dc39
https://github.com/RestKit/RestKit/issues/611#issuecomment-4858605
Updated my setter and getter so that the interface with rest of the modules remain same while internally we now depend upon NSManagedObjectID and avoid retaining of NSManageObject:
-(CSTaskAbstract*)task
{
CSTaskAbstract *theTask = nil;
if (self.taskObjectID)
{
NSManagedObjectContext *moc = [(CSAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
// https://github.com/RestKit/RestKit/commit/170060549f44ee5a822ac3e93668dad3b396dc39 &
// https://github.com/RestKit/RestKit/issues/611#issuecomment-4858605
NSError *theError = nil;
NSManagedObject *theObject = [moc existingObjectWithID:self.taskObjectID
error:&theError];
if ([theObject isKindOfClass:[CSTaskAbstract class]])
{
theTask = (CSTaskAbstract*)theObject;
}
}
return theTask;
}
-(void)setTask:(CSTaskAbstract *)inTask
{
if (inTask!=self.task)
{
// Consequences of retaining a MO when it is detached from its MOC
[self setTaskObjectID:[inTask objectID]];
}
}
The above is the first half of the problem solved. We need to find out dependency in suspicious parts of our app and eliminate.
There was some other problem too, instruments -> allocations is a good source to find out which modules are actually retaining the managed objects, the exception object would have details about which managed object is creating the problem, filter results for that object as shown below:
We were performing KVO on a managed object. KVO retains the observed managed object and hence the exception is thrown and it's back trace would not be from within our project. These are very hard to debug but guess work and tracking the object's allocation and retain-release cycle will surely help. I removed the KVO observation part and it all started working.

Resources