So I cheated on this for a while, including trying to @Aderstedt's suggestion. This approach did not work, because the fake notification, it seems, just tells the receiving context “hey, check with the permanent stores, I updated them!”, When in fact I did not, because they are not. In the end, I found an approach that worked. Unfortunately, it relies only on Lion functions, so I'm still looking for a way to do this, which does not require Lion.
Background
I wanted to work with the NSPersistentDocument approach. Although I have not found this documented anywhere, I found several forum posts and experienced a bunch of empirical evidence that you cannot call -[NSManagedObjectContext save:] in a context related to NSPersistentDocument. As mentioned in the question, if you call this before the document has ever been saved, it will not have stores, so a failure will occur. Even after the repository exists, preserving the context directly (and not through the document preservation API), you actually change the view on the disk behind the NSPersistentDocument, and you get a document that spreads sheets that say:
The file was modified by another application.
In short, an NSPersistentDocument expects control over the save action of the associated NSManagedObjectContext itself.
The front is also worth mentioning: the goal here was to make sure that the context used in the user interface would not cause a single (or at least minimal) I / O to remain responsive. In the end, I determined that I should have 3 contexts. One context, owned by NSPersistentDocument, that will be responsible for performing file I / O in conjunction with the document. A second, read-only, context-friendly user interface binding. (I understand that many people want the user interface to distort the model, so it might be less exciting for them, but it wasn’t a requirement for me.) And the third context is for use in a background thread that asynchronously downloads data from the Internet to a service and I hope it pushes it into other contexts, so that it can be saved to disk and presented in the user interface without potentially blocking the user interface during network input / output.
Lion-only solution
The new parent / child function NSManagedObjectContext in the Lion CoreData implementation is ideal for this. I replaced NSPersistentDocument NSManagedObjectContext with a new MOC concurrency of type NSPrivateQueueConcurrencyType. This will be the “root” context. Then I created a UI context with the NSMainQueueConcurrencyType concurrency and made it a child of the root context. Finally, I made the network boot context the NSPrivateQueueConcurrencyType context, which is a child of the user interface context. How it works, we run the network boot operation in the background, it updates the network context. When this is done, it will keep the context. With parent / child relationships, saving the child context transfers the changes to the parent context (user interface context), but does not save the parent context in the repository. In my case, I also listen to the NSManagedObjectContextDidSaveNotification notification from the network context, and then also tell him that it also saves it (which will lead to changes from the user interface context in the root / disk context, but will not save it to disk).
At the end of this chain of events, all contexts are consistent, and we still did not force the main root context to be saved, and therefore we did not encounter NSPersistentDocument in its management role on disk.
One of the tricks is that if you want to prevent the saving of child contexts due to the cancellation of the cancellation (i.e. it was a network boot operation, nothing was canceled there), you need to disable the account in each parent context when propagating the changes in the chain.
Efforts to the Lion
I would really like to find a solution compatible with pre-Lion for this problem. I tried a few things before giving up. First, I tried to associate the in-memory storage with the PSC in the init document so that I could execute the NSManagedObjectContext, saving before saving the document, and then transfer the storage to the memory the first time it was saved. This part worked just fine. But as soon as the storage on disk existed, this approach was fictitious, because after saving it to disk we have the same problem when any saving of MOCs connected to the PSC belonging to NSPsistentDocument must be performed by a document.
I also tried to crack the mechanism for moving 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 arising on the horizon with this approach. In particular, it’s easy to transfer these changes once, but what if it changes again before the save operation? Then I would be stuck keeping a long-running OID mapping in the source context to the OID in the destination context (s). It was awfully fast. If anyone is interested, here is 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 function, I quickly lost interest in the pre-Lyon solution. I am starting to realize that the pre-Lion solution will effectively "Do not use NSPersistentDocument". As far as I can tell, all these pain points go away if I refuse this requirement. Without this, you can save contexts and transfer stores whenever you want, but, of course, you have to do it all yourself.