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

KVO and Core Data - Self observing managed object

我觉得这个问题很简单也很常见,但我还是不明白为什么它不起作用。让我来揭露上下文:

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

这样的文档会很多,我要通过Identifier来搜索,我也会用很多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: 文档 class 继承自 NSManagedObject

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

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

这是 Apple 的官方 API NSManagedObject class 文档的摘录:

"You are also discouraged from overriding initWithEntity:insertIntoManagedObjectContext:, or dealloc. Changing values in the initWithEntity:insertIntoManagedObjectContext: method will not be noticed by the context and if you are not careful, those changes may not be saved. Most initialization customization should be performed in one of the awake… methods."

因此,鉴于您可能应该在 awakeFromInsert: 或 awakeFromFetch: 中添加这些 KVO 观察(然后在 didTurnIntoFault 中删除这些观察者)重写的子方法class,也许您可​​以避免所有这些开销根据影响您计算的因素添加和删除观察者 属性.

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

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

创建 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 个自定义 setter 并从它们中调用 updateDocumentId