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

标签 iphone objective-c core-data

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

假设我们有一个包含三个实体的 Core Data 模型:Department、Employee 和 DepartmentSummary(表示有关部门的一些统计信息的某种实体)。为了简单起见,我们只有一对一的关系:

DepartmentSummary   Department        Employee
---------------------------------------------------------
                    employee  <---->  department
department  <---->  summary

这就是模型中的全部内容。在 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"];

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

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

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");
}

这个断言失败了。确实,如果我们在设置摘要的部门后打印部门和摘要对象,我们会得到

entity: DepartmentSummary; 
    id: ..DepartmentSummary/..AA14> ; 
  data: { 
    department = "..Department/..AA13>";
  }

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

entity: Department; 
    id: ..Department/..AA13> ; 
  data: {
    employee = "..Employee/..AA12>";
    summary = nil;
  }

部门(带有 nil 摘要)。但是,如果我们延迟对 createSummary 的调用,使其在运行循环的下一次迭代之前不会运行:

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

然后一切都按预期工作。

延迟断言反而没有帮助:逆关系确实没有在对象图中设置,尽管它确实设置了在数据库中(如果你要保存数据库,然后重新启动应用程序,现在突然出现相反的关系)。

这是 Core Data 中的错误吗?这是我错过的记录在案的行为吗?我是否以非预期的方式使用了 Core Data?

请注意,KVO 处理程序被调用而 Core Data(自动)设置一个(其他)逆:我们手动设置部门的 employee 字段,Core Data 自动设置员工的 department 字段,然后触发 KVO 处理程序。也许这对 Core Data 来说太多了无法处理 :) 事实上,当我们设置

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

相反,一切再次按预期工作。

任何指针将不胜感激。

最佳答案

这是一个经典的 Core Data 问题。文档明确指出:

Since Core Data takes care of the object graph consistency maintenance for you, you only need to change one end of a relationship and all other aspects are managed for you.

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

我对你的问题的回答是:

Is this a bug in Core Data?

是的。

Is this documented behaviour which I have missed?

没有。

Am I using Core Data in ways it was not intended?

没有。

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

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

即,每当您更改关系时,将关系设置为反转。

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

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

最后一点是违反直觉的。一方面,根据文档,它似乎使代码复杂化并通过向可能是短的单行调用添加行来使其更长。但根据我的经验,它所做的是节省程序员前往 Core Data 编辑器以直观地寻找和确认模型关系的行程,这在时间上更有值(value)。最好是清楚明确,而不是对改变关系时应该发生的事情有一个心理模型。

我还建议向 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

如:

[department setValue:employee forKey:@"employee" inverse:@"department"];

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

简而言之:每次都显式处理您自己的所有关系。Core Data 在这方面不值得信赖。

关于iphone - 未设置反向关系(在 KVO 处理程序中),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7124678/

相关文章:

c++ - 使用委托(delegate)在 C++ 类和 Objective-C 类之间进行通信?

iphone - 在 CGPath 之间着色

iOS Buttons - 添加边框

objective-c - 核心数据属性唯一性

iphone - Cocos2d更新功能等效吗?

iphone - Xib 文件位于另一个 xib 中

当 native 闹钟响起时发送推送通知的 iOS 应用程序

ios - UIImageView 子类化

swift - 如何按创建 UITableViewCell 时计算的值对 UITableView 进行排序?

ios - 核心数据问题 : -[NSManagedObject setValue:]: unrecognised selector sent to instance