14
Oct
2011
 

Parent Watching Its Child

by Marcus Zarra

Recently on [StackOverflow](http://stackoverflow.com) I have seen several questions regarding the desire for a parent Entity to be updated whenever an attribute within the child has changed.

There are several different ways to solve this problem. The easiest is to have the child ping the parent whenever it changes and then the parent can update any values it needs to as a result of that ping.

Another solution that I considered was to have the parent observe the property on each child. Of course this requires additional code to handle when a child is removed from the parent and when a new child is added to the parent. Lots of code to manage and the potential for fragility is rather high.

The most interesting solution that I offered was to have the parent watch for change notifications and react to them. It is this solution that I will discuss in a little more depth.

## Change Notifications ##

Core Data produces several different notifications during its lifecycle. The most commonly used notification is NSManagedObjectContextDidSaveNotification. Whenever a NSManagedObjectContext saves it will fire two notifications, one before the save and one after the save. The one before the save does not contain any direct information about what is going to be saved although you can discover it yourself.

The second one, NSManagedObjectContextDidSaveNotification contains a reference to each entity that it saved; whether they are newly inserted entities, updated entities or deleted entities.

There is another notification that is not used nearly as often; NSManagedObjectContextObjectsDidChangeNotification. This notification is significantly more chatty than the other two. It potentially can fire every time an entity changes. More specifically it will fire at the end of each run loop where an entity has changed. While this notification is more chatty, it is more useful for this purpose.

## Why use NSManagedObjectContextObjectsDidChangeNotification? ##

Of the three, the NSManagedObjectContextObjectsDidChangeNotification is the most useful for a parent/child monitoring. Both NSManagedObjectContextDidSaveNotification and NSManagedObjectContextWillSaveNotification only fire when a NSManagedObjectContext has been asked to save. A save can be a very rare event; possibly only once during the life of the application.

However, NSManagedObjectContextObjectsDidChangeNotification fires frequently enough that we can use it without having to worry about when a save is going to occur. We can use it during the life of the application and keep a parent updated to the status of its children.

## An Example ##

A common example that I like to use while I am testing these theories is my very simple RSS reader. Those who have watched my [Core Data videos](http://ideveloper.tv/video/coredatacourse.html) are familiar with Zeader.

In Zeader, there are only two entities:

In this example we want the Server entity to be updated whenever the read attribute on a child has changed. To do that we are going to make a few enhancements to the Server entity subclass.

### -awakeFromInsert and -awakeFromFetch ###

The first change we need to make is with the awake methods. Whenever a Server entity is created or loaded we need to start listening for NSManagedObjectContextObjectsDidChangeNotification postings.

– (void)awakeFromInsert
{
[super awakeFromInsert];

NSString *name = NSManagedObjectContextObjectsDidChangeNotification;
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self
selector:@selector(changes:)
name:name
object:nil];
}

– (void)awakeFromFetch
{
[super awakeFromFetch];

NSString *name = NSManagedObjectContextObjectsDidChangeNotification;
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self
selector:@selector(changes:)
name:name
object:nil];
}

### -willTurnInfoFault ###

To be clean we need to stop listening whenever the Server is about to be removed from memory. Since it is inappropriate to override the -dealloc method of a NSManagedObject we need to do so in the -willTurnIntoFault method.

– (void)willTurnIntoFault
{
[super willTurnIntoFault];
NSString *name = NSManagedObjectContextObjectsDidChangeNotification;
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center removeObserver:self
name:name
object:nil];
}

This method of course, just like the two awake methods previously can be rolled up into just two lines. They are broken apart here to make it easier to consume on the web.

### -changes: ###

The last piece to the puzzle is the need to react to those changes as they are posted.

– (void)changes:(NSNotification*)notification
{
NSSet *objects = nil;
NSMutableSet *combinedSet = nil;
NSPredicate *predicate = nil;
NSSet *unreadItems = nil;

NSDictionary *userInfo = [notification userInfo];

objects = [userInfo valueForKey:NSInsertedObjectsKey];
combinedSet = [NSMutableSet setWithSet:objects];

objects = [[notification userInfo] valueForKey:NSUpdatedObjectsKey];
[combinedSet unionSet:objects];

objects = [[notification userInfo] valueForKey:NSDeletedObjectsKey];
[combinedSet unionSet:objects];

predicate = [NSPredicate predicateWithFormat:@”entity.name == %@ && server == %@”,
@”FeedItem”, self];
[combinedSet filterUsingPredicate:predicate];

if ([combinedSet count] == 0) {
return;
}

predicate = [NSPredicate predicateWithFormat:@”read == NO”];
unreadItems = [[self feedItems] filteredSetUsingPredicate:predicate];
[self setUnreadCount:[unreadItems count]];
DLog(@”server status changed %i”, [unreadItems count]);
}

First, we do not really care whether the entities are inserted, deleted or updated. We just need an answer to a simpler question: Are any of the entities that I care about in this change set? Since our question is easier, we first can lump all of the changes into a single set.

Once we have all of the changes in a single set we then filter that set on the important question. The predicate needs to be in a specific order; first we filter on the type of entity (FeedItem) we care about and then we filter on its relationship (is its Server me?).

This predicate reduces the NSMutableSet to only those FeedItem entities that are children of this instance of Server. If that resulting NSMutableSet is zero then there are no relevant changes and we return; done.

If there are changes we want to update our unreadCount. To do that we grab all of our feedItems and filter them with a simpler predicate of @"read == NO". The result of that filter can then be plugged into our unreadCount.

## Conclusion ##

There are a few potential performance hot spots in this example. However, the monitoring of children by the parent is always going to be a performance hotspot. Therefore, great care should be given whenever to adding a watcher like this.