OK, this is driving me nuts.
I have two threads using a UImanageddocument, one on the main context where the user makes selections, the background thread with its own moc synchronizes the data with a server according to timestamps.
All seems to be working well, however when I: 1. Add an object on the main context 2. Synchronize in background 3. Save from background 4. Try to change the same object again, now from the main context - main thread
I get an NSMergeConflict
I'm going to include some code of mine excluding a lot of irrelevant code, to show you how I initialize the contexts, hopefully someone can enlighten me. I know core data is tricky in these areas.
On the main thread (in applicationdidfinishloadingwithoptions):
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask] lastObject]; //get the default user documents folder
url = [url URLByAppendingPathComponent:DATABASENAME];
UIManagedDocument *doc = [[UIManagedDocument alloc] initWithFileURL:url];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
doc.persistentStoreOptions = options;
[doc.managedObjectContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
self.database=doc;
self.mainManagedObjectContext=self.database.managedObjectContext;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleDataModelChange:) name:NSManagedObjectContextObjectsDidChangeNotification object:self.database.managedObjectContext];
And later:
- (void)handleDataModelChange:(NSNotification *)note
{
[self save];
}
-(void) save
{
[self.database saveToURL:self.database.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success) {
batch_save=!success;
NSLog(@"save success %d",success);
}];
}
And on the background thread:
dispatch_queue_t fetchQ = dispatch_queue_create("syncing list", NULL);
dispatch_async(fetchQ, ^ // *********** BACKGROUND THREAD ***********
{
AppDelegate *delegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
NSManagedObjectContext *backgroundMOC2;
backgroundMOC2=[[NSManagedObjectContext alloc] init];
[backgroundMOC2 setPersistentStoreCoordinator:delegate.mainManagedObjectContext.persistentStoreCoordinator];
[backgroundMOC2 setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
[delegate.mainManagedObjectContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
[[NSNotificationCenter defaultCenter] addObserver:delegate.mainManagedObjectContext selector:@selector(mergeChangesFromContextDidSaveNotification:) name:NSManagedObjectContextDidSaveNotification object:backgroundMOC2];
** pseudo-code:
perform fetch request from CD
submit data to server with POST request (synchronously)
retrieve JSON reply from server
update what's needed in CD objects
** end of pseudo code
[backgroundMOC2 save:nil];
[[NSNotificationCenter defaultCenter] removeObserver:delegate.mainManagedObjectContext name:NSManagedObjectContextDidSaveNotification object:backgroundMOC2];
});
dispatch_release(fetchQ);
I have tried all types of merge policy constants to no avail.
I am getting this, and the file isn't saved:
conflictList = ( "NSMergeConflict (0x1a9ee1e0) for NSManagedObject (0x119aea80) with objectID '0x9dcec90 ' with oldVersion = 10 and newVersion = 11 and old object snapshot = {\n displayName = \"\";\n machineName = KIYGRDRTTDVLTQB;\n note = \"\";\n product = \"0x11967ab0 \";\n public = 1;\n published = 1;\n quantity = 3;\n registeredTo = \"\";\n registeredToEmail = \"\";\n registeredToNote = \"\";\n status = \"ON REGISTRY\";\n upDate = \"2013-03-07 10:22:01 +0000\";\n wishList = \"\";\n} and new cached row = {\n displayName = \"\";\n machineName = KIYGRDRTTDVLTQB;\n note = \"\";\n product = \"0x1a9ee3d0 \";\n public = 1;\n published = 1;\n quantity = 3;\n registeredTo = \"\";\n registeredToEmail = \"\";\n registeredToNote = \"\";\n status = \"ON REGISTRY\";\n upDate = \"2013-03-07 10:22:03 +0000\";\n wishList = \"\";\n}" ); }
BTW the only difference I see between the old and new objects are a pointer to "product". Could this maybe my problem?
Another possible clue is the fact that this happens only with new objects added, and only AFTER the background synchronization takes place. If I stop the app and reload it (reloading the persistent store) I can now edit that existing object with no problem, and synchronize it as many times as I like with no problem.
Thanks guys
OK friends,
After a month of breaking my mind over this - there is a solution, and nothing I would have guessed. I actually opened a support incident with Apple and they solved this.
So here goes:
When setting the merge policy for the main managed object context, there is a property called parentContext. It still isn't very clear to me what this property is since documentation is scarce on this. However this definitely solves MY problem. Please take into account that on my workflow, I create an NSManagedDocument and extract the context from it. I think most people do it the other way around (most don't use a manageddocument).
Instead of
[self.mainManagedObjectContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
I now use:
NSManagedObjectContext *parentContext = delegate.mainManagedObjectContext.parentContext;
[parentContext performBlockAndWait:^{
[parentContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
}];
The block thing is a precaution to make sure this is performed in the proper thread, the important thing is to set the merge policy to the parentContext of the desired context.
On a personal note, I must add two big boos: 1. Boo to me, for using NSManagedDocument although it is a bit outdated, just because I found good examples using it. 2. Big boo to Apple for very lacking documentation in the field. I spent weeks reading about ManagedObjectContexts and today was my first acquaintance with the parentContext property.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With