【问题标题】:Core data - Save with an array causing 'Illegal attempt to establish a relationship'核心数据 - 与导致“非法建立关系的尝试”一起保存的数组
【发布时间】:2016-02-20 11:02:34
【问题描述】:

在核心数据中,我有一个通过桥实体连接的关系

有问题的关系是卡 ->> 段 ->> 战士

  • 一个card 有很多segments
  • 一个segment有多个fighters一个
  • fighter 可以出现在一个segment

一个段(或 SegmentRow)附有一系列战斗机。

如下图所示;

应用程序当前的工作方式是,用户选择一名战斗机添加到分段行。一旦他们点击保存,分配到该行的分段行和战士将被保存。

但我不断收到关系错误;

非法尝试在对象之间建立“战士”关系 在不同的上下文中(来源 = 段 -- 名称:第 0 行,有 0 名战士 , 目的地 = 一些战士)'

一些注意事项:

卡片对象还不存在,这是因为需要先保存段;然后可以保存卡片对象。

所以我要保存的代码是这样的;

+ (void) saveSegments:(NSArray *)segments inContext:(NSManagedObjectContext *)context  withCompletion:(void (^)(BOOL success, NSError *error))completion
{
    int i = 0;
    for (FCSegmentRow *row in segments) {

        FCSegment *segment = [FCSegment MR_createEntityInContext:context];
        segment.name = [NSString stringWithFormat:@"Row %lu", (unsigned long)i];


        // Fighters
        NSMutableSet *fighters = [NSMutableSet set];

        for (FCFighter *fighter in row.fighters) {
            if (fighter) {
                [fighters addObject:fighter];
            }
        }

        segment.fighters = [NSSet setWithSet:fighters];


        i++;
    }
    [context MR_saveToPersistentStoreAndWait];


    // Output
    NSArray *allSegments = [self segmentsInContext:context error:nil];
    for (FCSegment *segment in allSegments) {
        NSLog(@"%@", [segment description]);
    }
}

FCSegmentRow 只是一个带有 NSArray *fighters 的简单 NSObject;

在我的代码中,我尝试设置一个可变集并将战斗机添加到该集,然后将其保存到核心数据。

此外,我尝试将保存放在一个块中并等待完成;但这个问题继续发生。

这似乎是因为战斗机与我正在创建的片段处于不同的上下文中。

所以,我的问题是——我如何存储片段与其战士(关系)

编辑:战士是在哪里以及如何创建的?

参考问题

  1. 战斗机位于自己的页面(视图控制器)上,并拥有自己的托管对象上下文。

  2. 管理分段的页面允许用户从战斗机列表中进行选择;这将启动战斗机页面并等待完成块以“返回”所选战斗机。

  3. 战士数据来自 Firebase,并将其加载到核心数据中。

战士视图控制器就是这样配置的;

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.title = @"Pick Fighter";

    self.tableView.delegate = self;
    self.tableView.dataSource = self;
    self.context = [NSManagedObjectContext MR_context];

    [self reloadData];
}

// ... 

-(void) reloadData
{
        self.fighterList = [FCFighter findFightersInContext:self.context];

    [self.tableView reloadData];
}

如上所示,战士视图控制器有自己的上下文

编辑: 补充说明:

一张卡片由许多段组成,我称之为段行。

在“分段行视图控制器”上,每个表格单元格有 2 个按钮(一个分段有 2 个战斗机),用户可以按下任一按钮并触发战斗机页面,让用户选择所需的战斗机。

我尝试将与段行视图控制器中相同的上下文传递到战斗机页面,但发生的情况是,一旦我按下第二个按钮,它就会丢失第一个按钮的数据。

IE: 按下按钮 A - 选择一名战士 .. Joe Bloggs 按下按钮 B - 选择一名战士 .. 迈克·史密斯 当我返回分段行时;按下按钮A的战斗机消失;当我检查数据时它的(空)。

启动战士页面的代码如下;

UIStoryboard *mainStoryboard = [UIStoryboard mainStoryboard];
    FCFightersTableViewController *vc = (FCFightersTableViewController *) [mainStoryboard instantiateViewControllerWithIdentifier:@"FCFightersTableViewController"];
    vc.tableCellSelectable = YES;
    vc.excludeFighters = [selectedFightersMutable copy];
    //vc.context = self.context;
    vc.completionBlock = ^(FCFightersTableViewController *vc, FCFighter *fighter) {

        NSLog(@"Picked fighter - %@ (uuid: %@)", fighter.name, fighter.uuid);

        [fightersInRow setObject:fighter atIndexedSubscript:fighterIdx];
        segmentRow.fighters = [fightersInRow copy];


        NSLog(@"selected fighter - %@ (uuid: %@)", fighter.name, fighter.uuid);

        [fightersInRow setObject:fighter atIndexedSubscript:fighterIdx];
        segmentRow.fighters = [fightersInRow copy];

        [self.tableView beginUpdates];
        [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
        [self.tableView endUpdates];
    };

【问题讨论】:

  • 在哪里以及如何创建战斗机对象?
  • 你是对的,所以你需要告诉我们更多关于你的背景和战士来自哪里......
  • 您好。战士在另一个页面上(视图控制器)。这个视图控制器有它自己的托管对象上下文。所以有3页。第一个是卡。二是段位管理,三是斗士。
  • 因为页面 A 启动页面 B;是否需要传递 Page A 的托管对象上下文?
  • @cardigan 分段和战斗机必须在相同的上下文中才能在它们之间建立关系。除非您出于其他原因需要两个上下文,否则是的,只需将上下文从一个 VC 传递到下一个 VC。如果您确实需要单独的上下文,请传递 NSManagedObjectIDs 并在第二个上下文中重新获取您需要的任何对象。

标签: objective-c core-data nsmanagedobjectcontext


【解决方案1】:

只是想知道为什么要使用多个上下文?它的管理方式更加复杂,而且 Ive 仅在使用多线程时才发现它是必要的——例如,在实例化大量对象时,可能会使 UI 的主线程变慢。 Coredata 几乎可以在一个上下文中完成它的工作,而且你不会注意到任何缓慢。管理多个上下文需要大量计划,在您的情况下可能完全没有必要(但如果您别无选择,请道歉)。

另外一件事——你不应该去创建事物的 NSSet,然后将它们添加到其他对象。 Coredata 涵盖了所有这些内容(特别是如果您从 coredata 自动创建对象)。 NSSet 将准备就绪,您只需在主上下文中创建对象并设置它们的关系属性 - 父级等。

【讨论】:

  • 我以为我应该有几个上下文,它们都引用了主要的默认上下文。如果我不需要为关系创建 NSSet(可变或其他),是否只是“addFighter”的一种情况——我试过这个,但它给出了与 illegal attempt of relationships 相同的错误
  • 你是说应该有一个上下文,也许在应用程序委托中定义并使用这个?我会试试的
  • 使用多个上下文是既定的最佳实践。上下文是轻量级的,应该代表更改的草稿。
  • 那么每个视图控制器都可以拥有自己的上下文吗?
  • @cardigan 在我看来,它的杀伤力太大了​​。只是没有必要。苹果也很好的简单方法是将托管对象上下文作为应用程序委托的属性。然后只需在应用程序中需要的任何位置导入应用程序委托并使用该上下文。更容易,完全符合苹果的最佳实践,并且会为您节省大量的头痛。拥有多个上下文也很好,因为您需要有充分的理由,例如线程。确实不需要每个视图控制器一个。
【解决方案2】:

我接受@pbasdf 的回答。

这就是我所做的。我没有使用全局上下文,而是保留它,以便每个视图控制器都有自己的 NSManagedObjectContext。

当用户在 SegmentRow(页面 A)上时,用户可以按下一个按钮来启动战士列表(页面 B);返回一个完成块;

// (Page A) fires Page B

    UIStoryboard *mainStoryboard = [UIStoryboard mainStoryboard];
        FCFightersTableViewController *vc = (FCFightersTableViewController *) [mainStoryboard instantiateViewControllerWithIdentifier:@"FCFightersTableViewController"];

        vc.completionBlock = ^(FCFightersTableViewController *vc, NSManagedObjectID *fighterObjectID) {

            FCFighter *fighter = [self.context objectWithID:fighterObjectID];
            NSLog(@"Picked fighter - %@ (uuid: %@)", fighter.name, fighter.uuid);

            [fightersInRow replaceObjectAtIndex:fighterIdx withObject:fighter];
            segmentRow.fighters = [fightersInRow copy];

            [self.tableView beginUpdates];
            [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
            [self.tableView endUpdates];
        };

请注意,我根据上述 cmets 将其从 setObject 更改为 replaceObject

我还得到了ManagedObjectID 并使用上下文来获取战斗机,根据 pbasdf 的回答。

// (Page B) fighters page fires the completionBlock
-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
 [tableView deselectRowAtIndexPath:indexPath animated:NO];

    FCFighter *fighter = nil;
        fighter = [self.fighterList objectAtIndex:indexPath.row];

    if (self.completionBlock)
    {
        self.completionBlock(self, fighter.objectID);
    }

    [self.navigationController dismissViewControllerAnimated:YES completion:^{
        [MBProgressHUD hideHUDForView:self.view animated:YES];
    }];
}

当我来拯救它时,我现在;

// Save the segments to Core data
    for (FCSegmentRow *row in segments) {

        FCSegment *segment = [FCSegment MR_createEntityInContext:context];
        segment.name = [NSString stringWithFormat:@"Row %lu", (unsigned long)i];

        [segment addFighters:[NSSet setWithArray:row.fighters]];
    }
    [context MR_saveToPersistentStoreAndWait];


    // Output log
    NSArray *allSegments = [self segmentsInContext:context error:nil];
    for (FCSegment *segment in allSegments) {
        NSLog(@"%@", [segment description]);
        for (FCFighter *fighter in [segment.fighters allObjects]) {
            NSLog(@"Fighter -- %@", fighter.name);
        }
    }

虽然我得到一个核心数据错误,但我会将其放在另一个问题中,当我点击输出日志时,它会显示给定段的战斗机。

我必须做一些检查,看看它是否正确地持久化和获取对象;但除此之外,我接受@pbasdf 的回答。

非常感谢

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多