Core-Data中的自定义设置方法


69

我需要foo在的子类中为字段(我们将其称为)编写自定义的setter方法NSManagedObjectfoo是在数据模型中定义的,并且Xcode分别自动生成了.h和.m文件中的@property@dynamic字段。

如果我这样写我的二传手:

- (void)setFoo: (NSObject *)inFoo {
    [super setFoo: inFoo];
    [self updateStuff];
}

然后在调用时收到编译器警告super

或者,如果我这样做:

- (void)setFoo: (NSObject *)inFoo {
    [super setValue: inFoo forKey: inFoo];
    [self updateStuff];
}

然后我陷入无限循环。

那么为NSManagedObject的子类编写自定义设置器的正确方法是什么?

Answers:


9

这是NSManagedObject.m文件中覆盖属性(不破坏KVO)的Apple方法:

@interface Transaction (DynamicAccessors)
- (void)managedObjectOriginal_setDate:(NSDate *)date;
@end

@implementation Transaction
@dynamic date;

- (void)setDate:(NSDate *)date
{
    // invoke the dynamic implementation of setDate (calls the willChange/didChange for you)
    [self managedObjectOriginal_setDate:(NSString *)date;

    // your custom code
}

managedObjectOriginal_propertyName是内置的魔术方法,您只需为其添加定义。如页面底部所示,macOS 10.12,iOS 10.0,tvOS 10.0和watchOS 3.0中的Core Data新增功能


好去处@malhal -我不知道在iOS的10这一变化
安德鲁Ebling

105

根据文档,它将是:

- (void) setFoo:(NSObject *)inFoo {
  [self willChangeValueForKey:@"foo"];
  [self setPrimitiveValue:inFoo forKey:@"foo"];
  [self didChangeValueForKey:@"foo"];
}

这是当然的,忽略的事实NSManagedObjects只是想NSNumbersNSDatesNSDatas,和NSStrings为属性。

但是,这可能不是最佳方法。由于您希望在foo属性值更改时发生某些事情,为什么不仅通过键值观察来观察它呢?在这种情况下,听起来像是“ KVO走的路”。


谢谢戴夫。抱歉,该字段实际上定义为,NSNumber *但我试图对此问题进行概括。我尝试了您上面建议的方法,但是收到编译器警告,我的类可能未响应-setPrimitivePositionX:。有任何想法吗?好主意重新。KVO。最好的注册地点在哪里?在- (void)awakeFromInsert?我要取消注册- (void)dealloc吗?
Andrew Ebling

好的,我@interface在.m文件中添加了一个专用部分,并修复了该警告,但代码仍未按预期运行。我需要调试!
Andrew Ebling

在进一步调查中,当我在对象上显式设置值时,将正确调用setter,但是当我使用NSUndoManager还原更改时,不会调用setter。在这种情况下,我猜想KVO是更好的全方位方法。
Andrew Ebling

1
如果您在Core Data Model中将属性设为瞬态,则值将自动恢复。如果您需要在撤消/重做中进行其他自定义处理,那么KVO是唯一的方法。如果要符合10.5标准,则需要重写-(void)_undoDeletions:(id)对NSManagedObjectContext的删除,如qr.cx/iZq中所示
Martin Brugger 2010年

相反的setPrimitiveFoo:,你可以做[super setPrimitiveValue:inFoo forKey:@"foo"];我同意志愿应该会更好,但它似乎在管理对象复杂,正确注册/注销志愿,我很担心我的情况下的性能(几十万分配的对象/释放没有foo改变)。
Abhi Beckert

19

这是我在的id属性上执行KVO的方式Photo : NSManagedObject。如果照片的ID更改,请下载新照片。

#pragma mark NSManagedObject

- (void)awakeFromInsert {
    [self observePhotoId];
}

- (void)awakeFromFetch {
    [self observePhotoId];
}

- (void)observePhotoId {
    [self addObserver:self forKeyPath:@"id"
              options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change
                       context:(void *)context {
    if ([keyPath isEqualToString:@"id"]) {
        NSString *oldValue = [change objectForKey:NSKeyValueChangeOldKey];
        NSString *newValue = [change objectForKey:NSKeyValueChangeNewKey];        
        if (![newValue isEqualToString:oldValue]) {
            [self handleIdChange];
        }
    }
}

- (void)willTurnIntoFault {
    [self removeObserver:self forKeyPath:@"id"];
}

#pragma mark Photo

- (void)handleIdChange {
    // Implemented by subclasses, but defined here to hide warnings.
    // [self download]; // example implementation
}

如果删除一个对象,则保存上下文(实际上已释放对象),调用撤消操作后,将丢失该观察。在10.6+中,您还可以在awakeFromSnapshotEvents中建立观察。为了向后兼容,请查看 github.com/mbrugger/CoreDataDependentProperties,它完全解决了所有这些问题。
马丁·布鲁格

3
从苹果的文档中,您应该在“ awakeFromFetch”和“ awakeFromInsert”上调用超级
Fervus 2014年

[newValue isEqualToString:oldValue]测试是不必要的,因为只有它们不相同时才会触发通知。
Elise van Looij

17

我认为有一个小错误:使用

 [self setPrimitiveValue:inFoo forKey:@"foo"];

代替

 [self setPrimitiveFoo:inFoo];

这对我有用。


谢谢马丁。正如你所说,志愿是要走的路(我在注册-(void)awakeFromFetch和注销中-(void)dealloc,现在我实现了这一点,它具有撤销工作。
安德鲁Ebling

7
不要使用-(void)dealloc取消注册,而是取消注册-(void)willTurnIntoFault中的观察值。否则,当对象变成故障时,您将收到不必要的通知。插入的新对象不会收到-(void)awakeFromFetch消息。也使用-(void)awakeFromInsert。
Martin Brugger 2010年

1
@Andrew Ebling,请回答您自己的问题,并提供解决方案的源代码。(可以随意更改变量名称等,但请保持其良好状态。)我正在努力做这件事。我通过阅读KVC上的链接来解决这个问题,但是了解您的解决方案将非常有帮助!:)
ma11hew28 2011年

0

1-n(我假设是nm)关系的方法如下:

假设在名为“学校”的对象中关系名称被称为“学生”。

首先,您需要为NSMutableSet定义原始访问器方法。Xcode不会自动为您生成这些。

@interface School(PrimitiveAccessors)
- (NSMutableSet *)primitiveStudents;
@end

接下来,您可以定义访问器方法。在这里,我将覆盖设置器。

- (void)addStudentsObject:(Student *)student
{
  NSSet *changedObjects = [[NSSet alloc] initWithObjects:&student count:1];

  [self willChangeValueForKey:@"students"
              withSetMutation:NSKeyValueUnionSetMutation
                 usingObjects:changedObjects];

  [[self primitiveStudents] addObject:value];

  [self didChangeValueForKey:@"students"
             withSetMutation:NSKeyValueUnionSetMutation
                usingObjects:changedObjects];
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.