KVO 核心数据和原始访问器

Posted

技术标签:

【中文标题】KVO 核心数据和原始访问器【英文标题】:KVO Core Data and primitive accessors 【发布时间】:2013-01-04 03:25:19 【问题描述】:

我的 iPhone 应用程序核心数据属性的自定义访问器方法存在问题。我想做的不仅仅是拉出原始类型并返回它。我有一个可能的活动类型的 NSSet,如果对象没有原始类型,那么我想查看该集合并返回与我的类中的另一个变量匹配的活动类型。

以下是我为此编写的代码。代码的问题是我对[self willChangeValueForKey:@"type"] 的调用导致了一个无限循环,程序不断吐出NSLog“将类型设置为...” - 可能是因为我从访问器中调用willChange?

如果我取出[self willChangeValueForKey:@"type"] 我没有得到循环并且程序运行良好,并且下次我尝试访问对象上的类型时,原始类型会被正确调用。但是,当我告诉我的 ManagedObjectContext 是时候保存它时,它会告诉我任何对象都没有更改 - 因此,由于我没有调用 willChangeValueForKey - 对原始类型的更改永远不会为对象保留。

- (ActivityType *)type 

[self willAccessValueForKey:@"type"];
ActivityType *myType = [self primitiveType];
[self didAccessValueForKey:@"type"];

if (myType != nil) 
    NSLog(@"Use primitive (%@)", [myType myDescription]);

 else 

    // 1) find type
    NSSet *types = self.myActivityTypes;

    NSSet *foundTypes = [types objectsPassingTest:^BOOL(id obj, BOOL *stop) 
        ActivityType *object = (ActivityType *) obj;

        return ([object.typeName isEqualToString:self.activityTypeName]);

    ];

    myType = [foundTypes allObjects][0];

    NSLog(@"Setting type to %@", [myType myDescription]);

    // in this case we should not alert anyone since we are inside the getter?
    [self willChangeValueForKey:@"type"];
    [self setPrimitiveType:myType];
    [self didChangeValueForKey:@"type"];




return myType;


【问题讨论】:

你为什么不直接使用 set 访问器,例如self.type = myType?您正在通过执行您正在执行的操作绕过访问器。 使用 self.type = myType;将程序置于与发出 willChangeValueForKey 警告相同的无限循环中。 好吧,我想是有道理的。我有一个答案给你,稍后会完善它并发布。 【参考方案1】:

托管对象属性观察机制将使用访问器检查新旧值。由于您正在更改 getter 中的值,因此将再次调用 getter,因此您的无限循环开始。

这种延迟加载实际上不适用于托管对象。您可以在其他地方设置活动类型,例如 awakeFromFetch 或在设置活动类型名称时,或者如果不合适,则实现单独的访问器:

-(ActivityType*)calculatedActivityType

    ActivityType *myType = self.activityType;
    if (myType) return myType;

    NSSet *types = self.myActivityTypes;

    NSSet *foundTypes = [types objectsPassingTest:^BOOL(id obj, BOOL *stop) 
        ActivityType *object = (ActivityType *) obj;

        return ([object.typeName isEqualToString:self.activityTypeName]);

    ];

    myType = [foundTypes allObjects][0];
    self.activityType = myType;
    return myType;

这不会导致任何循环,因为您不在任何访问器中。

【讨论】:

非常感谢 - 这是一个很好的解决方案! 记录一下,如果其他人将来尝试这样做 - 使用 awakeFromFetch 似乎是一个很好的解决方案,并且在实施和测试时,它似乎数据已保存到数据库中 - 至少对象看起来很脏,managedObjectContext.hasChanges 是真的。但是当程序退出并重新运行时,对象仍然是动态创建的,所以看起来在 awakeFromFetch 中设置对象的一部分可能不是一个好主意。可能此时对象处于不一致状态。但是calculatedActivityType 的建议很有魅力【参考方案2】:

另一种解决方案是在不更改底层值类型的情况下由访问器返回新类型。并且...在当前队列上异步调度更改。

dispatch_async(dispatch_get_current_queue(), ^
    [self willChangeValueForKey:@"type"];
    [self setPrimitiveType:myType];
    [self didChangeValueForKey:@"type"];
);

这并不完美,我可以想象这可能会导致您的“查找代码”被执行多次。

【讨论】:

以上是关于KVO 核心数据和原始访问器的主要内容,如果未能解决你的问题,请参考以下文章

核心数据原始访问器

如何使用核心数据的添加和删除(NSSet)访问器方法?

CoreData - 原始访问器作为属性?

Core Data 无法生成原始访问器

核心数据访问器 - 删除前

原iOS中KVC和KVO的区别