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

时间:2011-08-19 16:36:31

标签: 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很简单:它创建一个新的摘要对象并将其与部门关联,然后检查从部门到摘要对象的反向关系是否也已设置:< / p>

- (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,那么它将在runloop的下一次迭代之前运行:

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

然后一切都按预期工作。

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

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

请注意,KVO处理程序被称为,而Core Data(自动)设置(其他)反向:我们手动设置部门的employee字段,Core Data自动设置员工的{ {1}}字段,然后触发KVO处理程序。也许这对Core Data来说太过分了:)确实,当我们设置

department

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

任何指针都将不胜感激。

2 个答案:

答案 0 :(得分:4)

这是一个经典的核心数据问题。文档明确指出:

  

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

然而,在实践中,这是一个光头谎言,因为它是不可靠的。

我对你问题的回答是:

  

这是Core Data中的错误吗?

YES。

  

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

NO。

  

我是否以不想要的方式使用Core Data?

NO。

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

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

即,无论何时改变关系,都要自己设置关系。

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

  1. 它100%的工作时间。
  2. 它使代码更具可读性。
  3. 最后一点是违反直觉的。一方面,它似乎使代码复杂化,并通过在文档中添加简单的单行调用来增加行数。但根据我的经验,它所做的是保存程序员前往Core Data编辑器的行程,以便在视觉上追捕并确认模型关系,这在时间上更有价值。最好是清楚明确地与改变关系时应该发生的事情的心理模型进行比较。

    我还建议在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"];
    

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

    简而言之: 每次都明确处理您自己的所有关系。核心数据在这方面并不值得信赖。

答案 1 :(得分:0)

如何在插入后立即保存ManagedObjectContext?