NSInvalidArgumentException: Illegal attempt to communicate between objects in different contexts

I have an application based on the CoreDataBooks example that uses addingManagedObjectContext to add an Ingredient to a Cocktail to undo the whole add. CocktailsDetailViewController , in turn, calls BrandPickerViewController to (optional), setting the brand name for this ingredient. Cocktail , Ingredient and Brand are all NSManagedObjects . Cocktail requires at least one Ingredient ( baseLiquor ) to be baseLiquor , so I create it when Cocktail is created.

If I add a Cocktail to the CocktailsAddViewController : CocktailsDetailViewController (merging into the Cocktail managed object context when saved) without setting baseLiquor.brand , then it works to set the Brand from the collector (also stored in the Cocktails managed context) later from the CocktailsDetailViewController .

However, if I try to set baseLiquor.brand in CocktailsAddViewController , I get:

Application termination due to an uncaught exception "NSInvalidArgumentException", reason: "Illegal attempt to create a brand relationship between objects in different contexts

From this question, I understand that the problem is that the Brand is stored in the managedObjectContext application, and the recently added Ingredient and Cocktail stored in addingManagedObjectContext , and that passing the ObjectID instead will prevent a crash.

I don’t understand how to implement the collector in the general case, so that all ingredients ( baseLiquor , mixer , garnish , etc.) can be set at the time of adding, one by one from the CocktailsDetailViewController after creating the Cocktail . In other words, following the CoreDataBooks example, where and when would an ObjectID be converted to an NSManagedObject from the parent MOC in cases of adding and editing? IPD

UPDATE . Here's the method added:

 - (IBAction)addCocktail:(id)sender { CocktailsAddViewController *addViewController = [[CocktailsAddViewController alloc] init]; addViewController.title = @"Add Cocktail"; addViewController.delegate = self; // Create a new managed object context for the new book -- set its persistent store coordinator to the same as that from the fetched results controller context. NSManagedObjectContext *addingContext = [[NSManagedObjectContext alloc] init]; self.addingManagedObjectContext = addingContext; [addingContext release]; [addingManagedObjectContext setPersistentStoreCoordinator:[[fetchedResultsController managedObjectContext] persistentStoreCoordinator]]; Cocktail *newCocktail = (Cocktail *)[NSEntityDescription insertNewObjectForEntityForName:@"Cocktail" inManagedObjectContext:self.addingManagedObjectContext]; newCocktail.baseLiquor = (Ingredient *)[NSEntityDescription insertNewObjectForEntityForName:@"Ingredient" inManagedObjectContext:self.addingManagedObjectContext]; newCocktail.mixer = (Ingredient *)[NSEntityDescription insertNewObjectForEntityForName:@"Ingredient" inManagedObjectContext:self.addingManagedObjectContext]; newCocktail.volume = [NSNumber numberWithInt:0]; addViewController.cocktail = newCocktail; UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:addViewController]; [self.navigationController presentModalViewController:navController animated:YES]; [addViewController release]; [navController release]; } 

and here is the NSFetchedResultsController site of Brand (this NSFetchedResultsController maintained by the application managed object context:

 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; cell.accessoryType = UITableViewCellAccessoryCheckmark; if ([delegate respondsToSelector:@selector(pickerViewController:didFinishWithBrand:forKeyPath:)]) { [delegate pickerViewController:self didFinishWithBrand:(Brand *)[fetchedResultsController objectAtIndexPath:indexPath] forKeyPath:keyPath]; // 'keyPath' is @"baseLiquor.brand" in the crash } } 

and finally the delegate implementation:

 - (void)pickerViewController:(IngredientsPickerViewController *)pickerViewController didFinishWithBrand:(Brand *)baseEntity forKeyPath:(NSString *)keyPath { // set entity [cocktail setValue:ingredient forKeyPath:keyPath]; // Save the changes. NSError *error; if (![cocktail.managedObjectContext save:&error]) { // Update to handle the error appropriately. NSLog(@"Unresolved error %@, %@", error, [error userInfo]); exit(-1); // Fail } // dismiss picker [self.navigationController popViewControllerAnimated:YES] } 

EVEN MORE

I'm doing a progess based on Marcus's suggestions - I matched addingManagedObjectContexts to the parent managed ObjectContext and wrapped everything in begin/endUndoGrouping to handle undo and save.

However, the object to be created is located in the NSFetchedResultsController , so when the user clicks the β€œ+” button to add a Cocktail , the object will briefly appear in the table (possibly to be destroyed) as shown as a modal view submission controller. The MDN example is Mac-based, so it does not affect the behavior of this user interface. What can I do to avoid this?

+4
source share
2 answers

It looks like you are creating two different Core Data stacks ( NSManagedObjectContext , NSManagedObjectModel and NSPersistentStoreCoordinator ). What you want to do from the example, just create two instances of NSManagedObjectContext pointing to the same NSPersistentStoreCoordinator . This will solve this problem.

Think of NSManagedObjectContext as a scratch. You can have as much as you want, and if you throw it away before you save it, the data contained in it will disappear. But they are all stored in the same place.

Update

CoreDataBooks, unfortunately, is a really terrible example. However, for your problem, I suggest removing the creation of additional context and see if an error has occurred. Based on the code you posted, and I assume that the code you copied directly from the Apple example, the double context, while almost useless, should work fine. Therefore, I suspect there is something else in the game.

Try using one context and see if the problem persists. You may have another interesting but subtle mistake that gives you this error; perhaps something was overloaded somewhere or something like that. But the first step is to remove the double context and see what happens.

update 2

If it crashes even with one MOC, your problem has nothing to do with contexts. What error do you get with one MOC? When we resolve this, we will solve your entire problem.

For a better solution, use NSUndoManager . For this he is intended. Apple REALLY does not recommend recommending multiple MOCs in its example.

I recently answered a question about using NSUndoManager with Core Data, but you can also see some of my MDN articles for an example.

+6
source

Yes, you definitely do not want to cross context boundaries when setting up relationships between objects; they should both be in the same NSManagedObjectContext. The old EOF had APIs to eliminate objects in different contexts, but CoreData doesn't seem to have an equivalent.

0
source

Source: https://habr.com/ru/post/1303660/


All Articles