KVO 和 Core Data - 自观察托管对象

Posted

技术标签:

【中文标题】KVO 和 Core Data - 自观察托管对象【英文标题】:KVO and Core Data - Self observing managed object 【发布时间】:2015-06-22 09:07:38 【问题描述】:

我认为这个问题很简单也很常见,但我仍然不明白为什么它不起作用。让我暴露上下文:

假设我有一个不错的核心数据模型,其中包含一个名为 Document 的实体。该文档有类型、日期、编号和版本...例如,类型:D,日期:17-10-2015,编号:24 和版本 3。 本文档具有使用这四个值计算的标识符:D20151017-24-R03

会有很多这样的文档,我必须通过它的标识符来搜索它们,我也会使用很多NSFetchedResultsController。所以暂时的可能性就出来了。

这就是我所做的。先注册观察四个相关属性:

- (instancetype)initWithEntity:(NSEntityDescription *)entity insertIntoManagedObjectContext:(NSManagedObjectContext *)context 
    self = [super initWithEntity:entity insertIntoManagedObjectContext:context];

    if (self) 
        [self addObserver:self forKeyPath:_Property(documentTypeRaw) options:0 context:KVODocumentIdContext];
        [self addObserver:self forKeyPath:_Property(date) options:0 context:KVODocumentIdContext];
        [self addObserver:self forKeyPath:_Property(number) options:0 context:KVODocumentIdContext];
        [self addObserver:self forKeyPath:_Property(version) options:0 context:KVODocumentIdContext];
    

    return self;

然后,在释放时注销:

- (void)dealloc 
    [self removeObserver:self forKeyPath:_Property(documentTypeRaw) context:KVODocumentIdContext];
    [self removeObserver:self forKeyPath:_Property(date) context:KVODocumentIdContext];
    [self removeObserver:self forKeyPath:_Property(number) context:KVODocumentIdContext];
    [self removeObserver:self forKeyPath:_Property(version) context:KVODocumentIdContext];

最后,管理通知:

- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 
    if (context == KVODocumentIdContext) 
        [self updateDocumentId];
    
    else 
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    

就在这里updateDocumentId

- (void) updateDocumentId 
    NSString * prefix = [self documentTypePrefix:self.documentTypeRaw];
    NSString * date = [self.date documentIdFormat];
    NSString * number = [NSString stringWithFormat:@"%.2d",[self.number shortValue]];
    NSString * version = [self.version isEqualToNumber:@0]?@"":[NSString stringWithFormat:@"-R%.2d",[self.version shortValue]];

    self.documentId = [NSString stringWithFormat:@"%@%@-%@%@",prefix,date,number,version];

对我来说,这应该是完美的......但是......它没有......

我有一个很好的:

failed: caught "NSInternalInconsistencyException", "<MBSDocument: 0x7fd9dbb45f40> (entity: MBSDocument; id: 0x7fd9dbb3cd00 <x-coredata:///MBSDocument/tB55CB581-AEC0-4211-A78A-7C48377BACC2612> ; data:
...
An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
Key path: date
Observed object: <MBSDocument: 0x7fd9dbb45f40> (entity: MBSDocument; id: 0x7fd9dbb3cd00 <x-coredata:///MBSDocument/tB55CB581-AEC0-4211-A78A-7C48377BACC2612> ; data:
...

我尝试了很多方法,其中包括在observeValueForKeyPath:ofObject:change:context: 中删除对super 的调用,或者在init 中注册等。但没有任何效果。好吧,我们将不胜感激。

提前致谢。

编辑:这是定义上下文的方式:

static void * KVODocumentIdContext = &KVODocumentIdContext;

编辑2:文档类继承自NSManagedObject

【问题讨论】:

你是如何使用上下文进行观察的。观察环境似乎有所不同。 根据nshipster.com/key-value-observing/#correct-context-declarations我以同样的方式声明了上下文(我已经编辑了帖子) 【参考方案1】:

第一件事:我不会覆盖 initWithEntity:

这是 Apple 官方 API 文档中 NSManagedObject 类的节选:

“您也不鼓励重写 initWithEntity:insertIntoManagedObjectContext: 或 dealloc。更改 initWithEntity:insertIntoManagedObjectContext: 方法中的值不会被上下文注意到,如果您不小心,这些更改可能不会被保存。大多数初始化应在其中一种唤醒…方法中执行自定义。”

因此,您可能应该在 awakeFromInsert: 或 awakeFromFetch: 中添加这些 KVO 观察结果:(然后在 didTurnIntoFault 中删除这些观察者)您的子类的重写方法,也许您可​​以免除添加和删除观察者的所有这些开销,具体取决于什么会影响您的计算属性。

如果影响计算属性的键路径不是很多关系,那么您不妨编写您的 documentID 计算属性 getter 访问器并实现类方法 +(NSSet *)keYPathsForValiesAffectingDocumentID,它返回一个包含键路径的 NSSet,如果更改将导致使用新值重新计算计算机属性。

【讨论】:

【参考方案2】:

KVODocumentIdContext 是问题区域,请详细说明和[超级观察..]方法

【讨论】:

我编辑了这篇文章来解释我是如何定义上下文的。 (我是根据nshipster.com/key-value-observing/#correct-context-declarations编辑的) 意思是同时删除上下文并检查是否一切正常?也分享班级等级制度 该类只是继承自NSManagedObject,这里没什么特别的...但是如果我删除上下文,我怎么知道我不会弄乱NSManagedObject 的内置KVO? 为什么需要调用 [super observeValue..]? 我认为不需要调用 'super' 并更仔细地使用上下文或删除它,你应该没问题【参考方案3】:

[self addObserver:self (如self.delegate = self)应该已经敲响了警钟!您永远不需要观察自己的属性,您可以自定义设置器。

创建setter,调用动态超级方法(由NSManagedObject自动添加),然后进行自定义工作,例如

@interface Event (DynamicAccessors)
- (void)managedObjectOriginal_setTimestamp:(NSDate *)timestamp;
@end

@implementation Event

- (void)setTimestamp:(NSDate *)timestamp
    [self managedObjectOriginal_setTimestamp:timestamp];
    
    // custom action for when the timestamp has been changed.


@end

因此,您将创建 4 个自定义设置器并从它们中调用 updateDocumentId

【讨论】:

以上是关于KVO 和 Core Data - 自观察托管对象的主要内容,如果未能解决你的问题,请参考以下文章

Swift 中带有 Core Data 的 KVO 通知

手动实现KVO

NotificationCenter KVC KVO Delegate总结

使用 xcode 和 core-data 中的代码创建托管对象

KVO-理解与简单使用

Rx 键值观察KVO的使用