未设置反向关系(在KVO处理程序中)

eds*_*sko 9 iphone core-data objective-c

我在Core Data中有一个非常奇怪的反向关系问题,我已经设法将我的问题简化为一个最小的例子,从基于支持Core Data的窗口模板的xcode中的新项目开始(即,那里很少).

假设我们有一个包含三个实体的核心数据模型:Department,Employee和DepartmentSummary(某种实体代表部门的一些统计信息).为简单起见,我们只有一对一的关系:

DepartmentSummary   Department        Employee
---------------------------------------------------------
                    employee  <---->  department
department  <---->  summary
Run Code Online (Sandbox Code Playgroud)

这就是模型中的全部内容.在application:didFinishLaunchingWithOptions:我们创建一个员工和一个部门并设置KVO:

NSManagedObject* employee = 
 [NSEntityDescription 
   insertNewObjectForEntityForName:@"Employee"
   inManagedObjectContext:[self managedObjectContext]];
[employee addObserver:self forKeyPath:@"department" options:0 context:nil];

NSManagedObject* department = 
  [NSEntityDescription 
    insertNewObjectForEntityForName:@"Department"
    inManagedObjectContext:[self managedObjectContext]];
[department setValue:employee forKey:@"employee"];
Run Code Online (Sandbox Code Playgroud)

KVO处理程序的目的是在设置员工部门后立即为部门创建摘要:

- (void) observeValueForKeyPath:(NSString *)keyPath 
                       ofObject:(id)object 
                         change:(NSDictionary *)change 
                        context:(void *)context 
{
     [self createSummary:object];
}
Run Code Online (Sandbox Code Playgroud)

createSummary很简单:它创建一个新的摘要对象并将其与部门关联,然后检查是否还设置了从部门到摘要对象的反向关系:

- (void) createSummary:(NSManagedObject*)employee 
{
    NSManagedObject* department = [employee valueForKey:@"department"];
    NSManagedObject* summary = 
     [NSEntityDescription 
       insertNewObjectForEntityForName:@"DepartmentSummary"
       inManagedObjectContext:[self managedObjectContext]];

    [summary setValue:department forKey:@"department"];

    NSAssert([department valueForKey:@"summary"] == summary, 
             @"Inverse relation not set");
}
Run Code Online (Sandbox Code Playgroud)

这个断言失败了.实际上,如果我们在汇总部门设置之后打印部门和汇总对象,我们就会得到

entity: DepartmentSummary; 
    id: ..DepartmentSummary/..AA14> ; 
  data: { 
    department = "..Department/..AA13>";
  }
Run Code Online (Sandbox Code Playgroud)

对于摘要,正如预期的那样,但是

entity: Department; 
    id: ..Department/..AA13> ; 
  data: {
    employee = "..Employee/..AA12>";
    summary = nil;
  }
Run Code Online (Sandbox Code Playgroud)

部门(nil摘要).但是,如果我们延迟调用,createSummary以便它在runloop的下一次迭代之前不会运行:

- (void) observeValueForKeyPath:(NSString *)keyPath 
                       ofObject:(id)object 
                         change:(NSDictionary *)change 
                        context:(void *)context 
{
     [self performSelector:@selector(createSummary:) 
                withObject:object 
                afterDelay:0];
}
Run Code Online (Sandbox Code Playgroud)

一切都按预期工作.

延迟断言反而没有帮助:反向关系确实没有在对象图中设置,虽然它确实在数据库中设置(如果你要保存数据库,并重新启动应用程序,现在突然反过来关系出现).

这是Core Data中的错误吗?这是我记错的记录行为吗?我是否以不想要的方式使用核心数据?

请注意,当Core Data(自动)设置(其他)反向时,将调用KVO处理程序:我们手动设置部门的employee字段,Core Data自动设置员工的department字段,然后依次触发KVO处理程序.也许这对Core Data来说太过分了:)确实,当我们设置时

[employee setValue:department forKey:@"department"];
Run Code Online (Sandbox Code Playgroud)

相反,一切都按预期工作.

任何指针将不胜感激.

SG1*_*SG1 4

这是一个经典的核心数据问题。该文档具体指出:

由于 Core Data 会为您处理对象图一致性维护,因此您只需更改关系的一端,所有其他方面都会为您管理。

然而,实际上这是一个赤裸裸的谎言,因为它是不可靠的。

我对你的问题的回答是这样的:

这是核心数据中的错误吗?

是的。

这是我错过的有记录的行为吗?

不。

我是否以非预期的方式使用核心数据?

不。

您已经为您的问题提供了“正确”的解决方案,与我每次更改我制作的每个核心数据应用程序中的关系值时使用的解决方案相同。对于数百种情况,推荐的模式是:

[department setValue:employee forKey:@"employee"];
[employee setValue:department forKey:@"department"];
Run Code Online (Sandbox Code Playgroud)

即,每当您更改关系时,请自行将关系设置为反向。

有人可能对这个主题有更多的了解,或者有更规范的形式来解决您的问题,但根据我的经验,没有办法保证关系是积极可用的,除非它是手动建立的(如您的问题所示)。更重要的是,该解决方案还有另外两个好处:

  1. 它100%的时间都有效。
  2. 它使代码更具可读性。

最后一点是违反直觉的。一方面,根据文档的说法,通过向可能是简短的单行调用的行中添加行,似乎使代码变得复杂并使其更长。但根据我的经验,它的作用是让程序员省去核心数据编辑器以直观方式寻找和确认模型关系的麻烦,这在时间上更有价值。最好是清晰明确,而不是对改变关系时应该发生的事情有一个心理模型。

我还建议向 NSManagedObject 添加一个简单的类别:

@interface NSManagedObject (inverse)

- (void)setValue:(id)value forKey:(NSString *)key inverseKey:(NSString *)inverse;

@end

@implementation NSManagedObject (inverse)

- (void)setValue:(id)value forKey:(NSString *)key inverseKey:(NSString *)inverse {
    [self setValue:value forKey:key];
    [value setValue:self forKey:inverse];
}

@end
Run Code Online (Sandbox Code Playgroud)

如:

[department setValue:employee forKey:@"employee" inverse:@"department"];
Run Code Online (Sandbox Code Playgroud)

有一些案例可以扩展该类别,但我会以完全不同的方式处理例如删除。

简而言之: 每次都明确地处理你自己的所有关系。 Core Data 在这方面并不值得信赖。