Custom setter methods in Core-Data

Objective CCore DataSetter

Objective C Problem Overview


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?

Objective C Solutions


Solution 1 - Objective C

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".

Solution 2 - Objective C

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
}

Solution 3 - Objective C

I think there is a slight mistake: use

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

instead of

 [self setPrimitiveFoo:inFoo];

this works for me.

Solution 4 - Objective C

Here is the Apple way for overriding NSManagedObject properties (without breaking KVO), in your .m file:

@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

Solution 5 - Objective C

There's a really handy Xcode Snippets menu (Xcode 12 has a + button in the top right) that has great snippets for overriding lots of common Core Data code, including KVO-compliant accessors for object-types (getters + setters).

Solution 6 - Objective C

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAndrew EblingView Question on Stackoverflow
Solution 1 - Objective CDave DeLongView Answer on Stackoverflow
Solution 2 - Objective Cma11hew28View Answer on Stackoverflow
Solution 3 - Objective CMartin BruggerView Answer on Stackoverflow
Solution 4 - Objective CmalhalView Answer on Stackoverflow
Solution 5 - Objective CPranav KasettiView Answer on Stackoverflow
Solution 6 - Objective CDavidView Answer on Stackoverflow