I am performing an NSOperation on a background thread that imports data into Core Data. I do this by first creating a record of the import (‘Import’) and then import an object that relates to the import record. If I save the managed object context, the next attempt to link an imported object to the import record will fail:
Illegal attempt to establish a relationship 'import' between objects in different contexts (source = <NSManagedObject: 0x1067bb730> (entity: Genre; id: 0x1053330c0 <x-coredata:///Genre/tC6A85CFE-3D0A-4E29-9186-4FD46104AEBC60> ; data: {
import = nil;
name = Polka;
}) , destination = <NSManagedObject: 0x106736170> (entity: Import; id: 0x103b571e0 <x-coredata://440D80CF-7C56-4B6F-9778-990032A76B8B/Import/p1> ; data: <fault>))
Here is the boiled-down code. I modified the code slightly to demonstrate the effect by adding a superfluous save; normally there’d be no reason to have one there.
NSError *writeError = nil;
TNAppDelegate *del = (TNAppDelegate *)[[NSApplication sharedApplication] delegate];
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] init];
[moc setPersistentStoreCoordinator:[del persistentStoreCoordinator]];
[moc setUndoManager:nil];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:@selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:moc];
// create import instance
NSManagedObject *import = [NSEntityDescription insertNewObjectForEntityForName:@"Import" inManagedObjectContext:moc];
[import setValue:[NSDate date] forKey:@"start"];
[moc save:&writeError];
[moc reset];
NSString *newGenre = [songDictItem objectForKey:@"Genre"];
NSManagedObject *newGenreObject = [NSEntityDescription insertNewObjectForEntityForName:@"Genre" inManagedObjectContext:moc];
[newGenreObject setValue:newGenre forKey:@"name"];
[newGenreObject setValue:import forKey:@"import"]; // BOOM!
UPDATE: By request, I am providing the code for mergeChanges:. It is found in the NSOperation. I have tried a number of variations on saving changes to the main MOC, but they’ve all ended the same way.
- (void)mergeChanges:(NSNotification*)notification
{
TNAppDelegate *del = (TNAppDelegate *)[[NSApplication sharedApplication] delegate];
if ([notification object] == [del managedObjectContext]) return;
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:@selector(mergeChanges:) withObject:notification waitUntilDone:YES];
return;
}
[[del managedObjectContext] mergeChangesFromContextDidSaveNotification:notification];
}
To this day I never figured out exactly what was going on. In the end I rebooted my project and designed it like this tutorial from the ground up. I had used it in the past for reference but somehow it took a full adoption of their code to work.