iphone - NS无效参数异常 : Illegal attempt to establish a relationship between objects in different contexts

标签 iphone objective-c core-data

我有一个基于 CoreDataBooks 示例的应用程序,它使用 addingManagedObjectContextIngredient 添加到 Cocktail 以撤消整个添加。 CocktailsDetailViewController 依次调用 BrandPickerViewController 来(可选)为给定成分设置品牌名称。 CocktailIngredientBrand 都是NSManagedObjectsCocktail 需要至少设置一种Ingredient (baseLiquor),所以我在创建Cocktail 时创建它.

如果我在 CocktailsAddViewController : CocktailsDetailViewController 中添加 Cocktail(在保存时合并到 Cocktail 托管对象上下文中)而不设置 baseLiquor.brand,然后它可以从 CocktailsDetailViewController 的选择器(也存储在 Cocktails 托管上下文中)设置 Brand

但是,如果我尝试在 CocktailsAddViewController 中设置 baseLiquor.brand,我会得到:

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Illegal attempt to establish a relationship 'brand' between objects in different contexts'

来自 this question我知道问题是 Brand 存储在应用程序的 managedObjectContext 中,新添加的 IngredientCocktail 是存储在 addingManagedObjectContext 中,传递 ObjectID 可以避免崩溃。

我不明白的是如何通用地实现选择器,以便所有成分(baseLiquormixergarnish、等) 可以在添加期间设置,也可以在创建 Cocktail 之后从 CocktailsDetailViewController 逐一设置。换句话说,按照 CoreDataBooks 示例,在添加和编辑情况下,ObjectID 何时何地从父 MOC 转换为 NSManagedObject? -IPD

更新 - 这是添加方法:

- (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's 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];

}

这是 Brand 选择器中的崩溃位置(此 NSFetchedResultsController 由应用委托(delegate)的托管对象上下文支持:

- (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
    }
}

最后是委托(delegate)实现:

- (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]
}

更多

我正在根据 Marcus 的建议取得进展——我将 addingManagedObjectContexts 映射到父 managedObjectContext 并将所有内容包装在 begin/endUndoGrouping 中以处理取消与保存.

但是,要创建的对象位于 NSFetchedResultsController 中,因此当用户点击“+”按钮添加 Cocktail 时,(可能是 - be-undone) 实体在呈现模态添加 View Controller 时短暂出现在 TableView 中。 MDN 示例是基于 Mac 的,因此它不涉及此 UI 行为。我该怎么做才能避免这种情况?

最佳答案

听起来您正在创建两个不同的核心数据堆栈(NSManagedObjectContextNSManagedObjectModelNSPersistentStoreCoordinator)。从示例中您想要做的只是创建两个指向相同 NSPersistentStoreCoordinatorNSManagedObjectContext 实例。这将解决此问题。

NSManagedObjectContext 视为便笺本。您可以拥有任意数量,如果您在保存之前将其丢弃,则其中包含的数据将消失。但它们都保存到同一个地方。

更新

不幸的是,CoreDataBooks 是一个非常糟糕的例子。但是,对于您的问题,我建议删除 附加上下文的创建并查看是否发生错误。根据您发布的代码,我假设您直接从 Apple 的示例中复制了代码,双重上下文虽然几乎没有用,但应该可以正常工作。因此,我怀疑还有其他因素在起作用。

尝试使用单个上下文并查看问题是否仍然存在。您可能有其他一些有趣但微妙的错误导致您出现此错误;也许是某个地方或某些地方的过度释放。但第一步是删除双重上下文,看看会发生什么。

更新2

如果即使只有一个 MOC 也会崩溃,那么您的问题与上下文无关。单个 MOC 出现的错误是什么?当我们解决这个问题时,我们将解决您的整个问题。

至于更好的解决方案,请改用 NSUndoManager。这就是它的设计目的。 Apple 真的不应该在他们的示例中推荐多个 MOC。

我最近在这里回答了一个关于将 NSUndoManager 与 Core Data 一起使用的问题,但您也可以查看我在 MDN 上的一些文章作为示例。

关于iphone - NS无效参数异常 : Illegal attempt to establish a relationship between objects in different contexts,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2416647/

相关文章:

ios - 找到最近的信标的正确方法

ios - UIImageView 无法在自定义的可滑动 UITableViewCell 中检测到长按手势,这是一个 ScrollView

iphone - iPhone:如何从viewForAnnotation方法的类中获取值?

ios - 核心数据在我删除它并重新启动我的应用程序后仍然存在

swift - 如何在swift中对核心数据内容进行加密

ios - 核心数据获取请求的复杂排序

ios - 为什么我不能将 WKWebView 保存到 [NSUserDefaults standardUserDefaults]?

ios - iPhone 6s Plus 无法打开设置

objective-c - 如何将 NSNumber 舍入为零小数位

iphone - 加载数据uitextfield