问题
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?
回答1:
Here is the Apple way for overriding NSManagedObject properties (without breaking KVO), in your .m:
@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
is a built-in magic method you just have to add the definition for. As seen at bottom of this page What's New in Core Data in macOS 10.12, iOS 10.0, tvOS 10.0, and watchOS 3.0
回答2:
According to the documentation, it'd be:
- (void) setFoo:(NSObject *)inFoo {
[self willChangeValueForKey:@"foo"];
[self setPrimitiveValue:inFoo forKey:@"foo"];
[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".
回答3:
Here's how I'm doing KVO on the id
attribute of a Photo : NSManagedObject
. If the photo's ID changes, then download the new photo.
#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
}
回答4:
I think there is a slight mistake: use
[self setPrimitiveValue:inFoo forKey:@"foo"];
instead of
[self setPrimitiveFoo:inFoo];
this works for me.
回答5:
Here's how you do it 1-n (and I presume n-m) relationships:
Lets assume the relationship name is called "students" in an object called "School".
First you need to define the primitive accessor methods for the NSMutableSet. Xcode will not automatically generate these for you.
@interface School(PrimitiveAccessors)
- (NSMutableSet *)primitiveStudents;
@end
Next you can define your accessor method. Here I'm going to override the 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];
}
来源:https://stackoverflow.com/questions/2971806/custom-setter-methods-in-core-data