核心数据 - 使用数组保存导致 'Illegal attempt to establish a relationship'
Core data - Save with an array causing 'Illegal attempt to establish a relationship'
在核心数据中,我有一个通过桥实体连接的关系
问题中的关系是卡片 -> 段 -> 战士
- 一个
card
有很多segments
- 一个
segment
有多个fighters
一个
- 一个
fighter
可以出现在一个segment
上
一个段(或 SegmentRow)附加了一组战士。
如下图所示;
该应用程序当前的工作方式是,用户选择一名战士添加到细分行中。一旦他们点击保存,段行和分配给该行的战士就会被保存。
但我一直收到关系错误;
Illegal attempt to establish a relationship 'fighters' between objects
in different contexts (source = Segment -- Name: Row 0 with 0 fighters
, destination = Some fighter)'
一些注意事项:
卡片对象还不存在,这是因为需要先保存段;然后可以保存卡片对象。
所以我要保存的代码是这样的;
+ (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;
在我的代码中,我尝试设置一个 mutable 集合并向该集合添加战士,然后将其保存到核心数据。
另外,我尝试将保存放在一个块中等待完成;但是这个问题继续发生。
这似乎是因为战斗机与我正在创建的片段处于不同的上下文中。
所以,我的问题是——如何存储片段及其战士(关系)
编辑:战士是在哪里以及如何创造的?
参考问题
战士在自己的页面(视图控制器)上,有自己的托管对象上下文。
管理段的页面允许用户从战士列表中挑选;这将启动战士页面并等待 "return" selected 战士的完成块。
战士数据来自Firebase,加载到Core data中。
战士视图控制器就这样配置了;
- (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];
}
如上所示,fighters 视图控制器有自己的上下文
编辑:
补充说明:
一张卡片由很多段组成,我称之为段行。
在 "segment row view controller" 上每个 table 单元格有 2 个按钮(一个段有 2 个战士),用户可以按下任一按钮并触发战士页面,让用户 select所需的战士。
我试图将与段行视图控制器中相同的上下文传递到战士页面,但发生的是当我按下第二个按钮时它丢失了第一个按钮的数据。
即:
按下按钮 A - 选择一名战士.. Joe Bloggs
按下按钮 B - 选择一名战士.. Mike Smith
当我return到段行时;按下按钮A的战斗机消失了;当我检查数据时 (null).
启动战士页面的代码如下;
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];
};
只是想知道为什么要使用多个上下文?它的管理方式更加复杂,我只发现在使用多线程时有必要 - 例如,在实例化大量对象时可能会使 UI 的主线程变慢。 Coredata 基本上可以在一个上下文中很好地完成它的工作,你不会注意到任何缓慢。管理多个上下文需要大量计划,并且在您的情况下可能完全没有必要(但如果您别无选择,我们深表歉意)。
另一件事 - 你不应该去创建事物的 NSSet 然后将它们添加到其他对象。 Coredata 涵盖了所有内容(特别是如果您从 coredata 自动创建对象)。 NSSet 将准备就绪,您只需要在主上下文中创建对象并设置它们的关系属性 - parents 等
我接受@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];
};
请注意,我根据上述评论将其从 setObject
更改为 replaceObject
。
根据 pbasdf 的回答,我也得到了 ManagedObjectID
并使用上下文来获取战斗机。
// (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 的回答。
非常感谢
在核心数据中,我有一个通过桥实体连接的关系
问题中的关系是卡片 -> 段 -> 战士
- 一个
card
有很多segments
- 一个
segment
有多个fighters
一个 - 一个
fighter
可以出现在一个segment
上
一个段(或 SegmentRow)附加了一组战士。
如下图所示;
该应用程序当前的工作方式是,用户选择一名战士添加到细分行中。一旦他们点击保存,段行和分配给该行的战士就会被保存。
但我一直收到关系错误;
Illegal attempt to establish a relationship 'fighters' between objects in different contexts (source = Segment -- Name: Row 0 with 0 fighters , destination = Some fighter)'
一些注意事项:
卡片对象还不存在,这是因为需要先保存段;然后可以保存卡片对象。
所以我要保存的代码是这样的;
+ (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;
在我的代码中,我尝试设置一个 mutable 集合并向该集合添加战士,然后将其保存到核心数据。
另外,我尝试将保存放在一个块中等待完成;但是这个问题继续发生。
这似乎是因为战斗机与我正在创建的片段处于不同的上下文中。
所以,我的问题是——如何存储片段及其战士(关系)
编辑:战士是在哪里以及如何创造的?
参考问题
战士在自己的页面(视图控制器)上,有自己的托管对象上下文。
管理段的页面允许用户从战士列表中挑选;这将启动战士页面并等待 "return" selected 战士的完成块。
战士数据来自Firebase,加载到Core data中。
战士视图控制器就这样配置了;
- (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];
}
如上所示,fighters 视图控制器有自己的上下文
编辑: 补充说明:
一张卡片由很多段组成,我称之为段行。
在 "segment row view controller" 上每个 table 单元格有 2 个按钮(一个段有 2 个战士),用户可以按下任一按钮并触发战士页面,让用户 select所需的战士。
我试图将与段行视图控制器中相同的上下文传递到战士页面,但发生的是当我按下第二个按钮时它丢失了第一个按钮的数据。
即: 按下按钮 A - 选择一名战士.. Joe Bloggs 按下按钮 B - 选择一名战士.. Mike Smith 当我return到段行时;按下按钮A的战斗机消失了;当我检查数据时 (null).
启动战士页面的代码如下;
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];
};
只是想知道为什么要使用多个上下文?它的管理方式更加复杂,我只发现在使用多线程时有必要 - 例如,在实例化大量对象时可能会使 UI 的主线程变慢。 Coredata 基本上可以在一个上下文中很好地完成它的工作,你不会注意到任何缓慢。管理多个上下文需要大量计划,并且在您的情况下可能完全没有必要(但如果您别无选择,我们深表歉意)。
另一件事 - 你不应该去创建事物的 NSSet 然后将它们添加到其他对象。 Coredata 涵盖了所有内容(特别是如果您从 coredata 自动创建对象)。 NSSet 将准备就绪,您只需要在主上下文中创建对象并设置它们的关系属性 - parents 等
我接受@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];
};
请注意,我根据上述评论将其从 setObject
更改为 replaceObject
。
根据 pbasdf 的回答,我也得到了 ManagedObjectID
并使用上下文来获取战斗机。
// (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 的回答。
非常感谢