【问题标题】:Custom setter methods in Core-DataCore-Data 中的自定义设置方法
【发布时间】:2011-02-27 15:30:18
【问题描述】:

我需要在我的NSManagedObject 子类中为一个字段(我们称之为foo)编写一个自定义setter 方法。 foo 在数据模型中定义,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 的子类编写自定义设置器的正确方法是什么?

【问题讨论】:

    标签: objective-c core-data setter


    【解决方案1】:

    根据the documentation,应该是:

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

    当然,这忽略了 NSManagedObjects 只需要 NSNumbersNSDatesNSDatasNSStrings 作为属性这一事实。

    但是,这可能不是最好的方法。既然您希望在 foo 属性的值发生变化时发生一些事情,为什么不直接用 Key Value Observing 观察它呢?在这种情况下,这听起来像是“KVO 的必经之路”。

    【讨论】:

    • 谢谢戴夫。抱歉,该字段实际上被定义为NSNumber *,但我试图概括这个问题。我尝试了您上面的建议,但我收到一个编译器警告,说我的班级可能无法响应-setPrimitivePositionX:。有任何想法吗?好主意。 KVO。哪里是注册的最佳地点?在- (void)awakeFromInsert?我会在- (void)dealloc 注销对吗?
    • 好的,我在 .m 文件中添加了一个私有 @interface 部分并修复了警告,但代码仍然没有按预期运行。我需要调试这个!
    • 在进一步调查中,当我在对象上显式设置值时,setter 被正确调用,但当我使用 NSUndoManager 还原更改时它不会被调用。在这种情况下,我猜 KVO 是一种更好的全方位方法。
    • 如果您在核心数据模型中使属性瞬态,则值会自动恢复。如果您需要在撤消/重做中进行额外的自定义处理,KVO 是唯一的方法。如果你想符合 10.5,你需要覆盖 - (void)_undoDeletions:(id)deletions of NSManagedObjectContext 就像在qr.cx/iZq
    • 你可以用setPrimitiveFoo:代替[super setPrimitiveValue:inFoo forKey:@"foo"]; 我同意KVO应该更好,但是在托管对象中正确注册/注销KVO似乎很复杂,而且我担心我的性能案例(数十万个对象在 foo 不变的情况下分配/释放)。
    【解决方案2】:

    这是我在Photo : NSManagedObjectid 属性上执行KVO 的方式。如果照片的 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 它正好解决了所有这些问题。
    • 从苹果的文档中,你应该在 "awakeFromFetch" 和 "awakeFromInsert" 上调用 super
    • [newValue isEqualToString:oldValue] 测试是不必要的,因为只有在它们不相同时才会触发通知。
    【解决方案3】:

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

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

    而不是

     [self setPrimitiveFoo:inFoo];
    

    这对我有用。

    【讨论】:

    • 谢谢马丁。正如你所说,KVO 是要走的路(我在-(void)awakeFromFetch 注册并在-(void)dealloc 取消注册,我现在已经实现了这个并且它可以与撤消一起使用。
    • 不要使用 -(void) dealloc 来取消注册,而是在 -(void) willTurnIntoFault 中取消注册观察。否则,当对象变成故障时,您将收到不必要的通知。插入的新对象不会收到 -(void) awakeFromFetch 消息。也使用 -(void) awakeFromInsert。
    • @Andrew Ebling,请回答您自己的问题并包含您的解决方案的源代码。 (随意更改变量名称等,但请保持良好状态。)我正在努力做这件事。我通过阅读 KVC 上的链接来解决这个问题,但是看到您的解决方案会非常有帮助! :)
    【解决方案4】:

    这是在您的 .m 文件中覆盖 NSManagedObject 属性(不破坏 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 是一个内置的 magic 方法,您只需为其添加定义。如本页底部所示What's New in Core Data in macOS 10.12, iOS 10.0, tvOS 10.0, and watchOS 3.0

    【讨论】:

    • 好点 @malhal - 我不知道 iOS 10 中的这种变化。
    【解决方案5】:

    有一个非常方便的 Xcode Snippets 菜单(Xcode 12 在右上角有一个 + 按钮)具有出色的 sn-ps 用于覆盖许多常见的 Core Data 代码,包括兼容 KVO 的访问器对象类型(getter + setter)。

    【讨论】:

      【解决方案6】:

      这是 1-n(我假设是 n-m)关系的方法:

      假设关系名称在名为“School”的对象中称为“students”。

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

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

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

      - (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];
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-11-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-11-22
        • 2012-04-26
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多