views:

229

answers:

2

I need to write a custom setter method for a field (we'll call it foo) in my subclass of NSManagedObject. foo is defined in the data model and Xcode has autogenerated @property and @dynamic fields in the .h and .m files respectively.

If I write my setter like this:

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

then I get a compiler warning on the call to super.

Alternatively, if I do this:

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

then I end up in an infinite loop.

So what's the correct approach to write a custom setter for a subclass of NSManagedObject?

+3  A: 

According to the documentation, it'd be:

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

This is, of course, ignoring the fact that NSManagedObjects only want NSNumbers, NSDates, NSDatas, and NSStrings as attributes.

However, this might not be the best approach. Since you want something to happen when the value of your foo property changes, why not just observe it with Key Value Observing? In this case, it sounds like "KVO's the way to go".

Dave DeLong
Thanks Dave. Apologies the field is actually defined as an `NSNumber *` but I was trying to generalise the problem.I tried what you suggested above, but I get a compiler warning that my class may not respond to `-setPrimitivePositionX:`. Any ideas?Good idea re. KVO. Where would be the best place to register? In `- (void)awakeFromInsert`? I'd de-register in `- (void)dealloc` right?
Andrew Ebling
OK, I added a private `@interface` section in the .m file and that fixed the warning, but the codes still not behaving as expected. I need to debug this!
Andrew Ebling
On further investigation the setter is getting called correctly when I explicitly set the value on the object, but it doesn't get called when I use the NSUndoManager to revert the change. In which case I'm guessing KVO is a better all-round approach.
Andrew Ebling
If you make the property transient in you Core Data Model, the values get reverted automatically. If you need additional custom processing in undo/redo KVO is the only way to go.If you want to be 10.5 compliant, you need to override - (void)_undoDeletions:(id)deletions of NSManagedObjectContext like in http://qr.cx/iZq
Martin Brugger
+2  A: 

I think there is a slight mistake: use

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

instead of

 [self setPrimitiveFoo:inFoo];

this works for me.

Martin Brugger
Thanks Martin. As you say, KVO is the way to go (I'm registering in `-(void)awakeFromFetch` and unregistering in `-(void)dealloc` and I've now implemented this and it works with undo.
Andrew Ebling
do not use -(void) dealloc to unregister, unregister observings in -(void) willTurnIntoFault instead. Otherwise you will get unnecessary notifications when an object ist turned into a fault.New objects inserted do not get a -(void) awakeFromFetch message. use -(void) awakeFromInsert too.
Martin Brugger