Quickshot: CoreData react when an object’s property changes

Core Data only supports a small subset of ObjectiveC’s data. That’s fine – for most apps, the data you need to save can be broken down into smaller pieces of primitive data (that Apple supports).

Real properties and simulated properties

We often need to mix “supported” and “unsupported” data in a single class. There’s a couple of ways you can implement this – the most correct is to extend your CoreData class with a category that simulated a property using Associated Objects. The quicker way is to do the same but instead of an Ass. obj, simply write a short “setter” and “getter” (or for read-only: only a “getter”) in the category:

@interface MyCoreDataClass
@property(nonatomic) String storedFilename;
@end

@interface MyCoreDataClass(NonStoredAdditions)
/** simulated, derived readonly property that CoreData doesn't know about */
-(BOOL) fileExists;
@end

@implementation MyCoreDataClass(NonStoredAdditions)
/** simulated, derived readonly property that CoreData doesn't know about */
-(BOOL) fileExists
{
    ... complex code here to check for existence of the file
    ... save the result into an Associated Object so we don't re-calc every time
    ... MUST invalidate this code each time .storedFilename changes!

    ... Hmm. How?
}
@end

Either way, you need to react to “the other variable changing”. The lame way of doing this is that every time you update the CoreData-backed fields, you also manually update the front-end fields – and vice-versa. It’s … lots of code, and easy to get wrong.

But that’s error-prone, and I hate writing boilerplate (it’s bound to cause a bug sooner or later).

ObjectiveC’s listen-to-other-property: KVO (with shortcut)

The correct ObjectiveC response is to use KVO, which is fiendishly over-complicated – but has a couple of shortcuts for the common-cases:

-(id) init
{
    self = [super init];
    if( self )
    {
	[self addObserver:self forKeyPath:@"storedFilename" options:NSKeyValueObservingOptionNew context:self];
    }
    return self;
}

- (void)dealloc
{
    // REQUIRED: or your app will crash horribly at runtime, at random times:
    [self removeObserver:self forKeyPath:@"storedFilename" context:self];
	
    [super dealloc];
}


- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if([keyPath isEqualToString:@"storedFilename"])
    {
        // react here by re-calculating the value of your derived property, and
        // ...re-store it into the ass. object
    }
}

Sadly, those shortcuts require you to put code in -init and -dealloc (otherwise you get random crashes, and some angry log messages). Argh! It’s a vicious circle of “CoreData doesn’t support X, so we do Y, but Y requires Z … which CoreData *also* doesn’t support”.

CoreData *does* support KVO listeners … just needs a tiny tweak

The correct way for Core Data is to use the “near equivalent” magic post-init methods, and the magic pre-dealloc method:

-(void)awakeFromFetch // called when pre-saved object is re-loaded from database
{
	[self addObserverForFileChanges];
}

-(void)awakeFromInsert // called when object is FIRST created
{
	[self addObserverForFileChanges];
}

-(void) addObserverForFileChanges
{
	[self addObserver:self forKeyPath:@"storedFilename" options:NSKeyValueObservingOptionNew context:self];
}
...
- (void) willTurnIntoFault // thanks to Ilya in comments. Not "- (void)didTurnIntoFault"
{
    [super willTurnIntoFault]; // thanks to Ilya in comments. Not "- (void)didTurnIntoFault"
    [self removeObserver:self forKeyPath:@"storedFilename" context:self];
}

For more info:

5 thoughts on “Quickshot: CoreData react when an object’s property changes

  1. Just what I needed – a nice simple tutorial for KVO for Core Data. Took me quite a few google searches to get such a concise explanation – thanks a lot!

  2. Actually you should use willTurnIntoFault to remove observers ( as confirmed by the apple docs you linked)
    Nice summary otherwise

  3. Another fairly important point – Apple states for both of the “awake” methods:

    “Important: Subclasses must invoke super’s implementation before performing their own initialization.”

Leave a Reply

Your email address will not be published. Required fields are marked *