Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Notify parent Entity when child Relationship Entity changes in Core Data

Is it possible to receive a callback or notification in the parent Entity when any one it's relationship objects changes? This works great when an attribute of the Entity changes. The following method...

- (void)didChangeValueForKey:(NSString *)key

is invoked on my Entity subclass. However this method is not invoked when an attribute in one of the relationships changes.

What I'm trying to do is update the timeStamp attribute on my parent Entity when any one of its attributes or relationship objects changes.

like image 789
Nate Potter Avatar asked Oct 11 '11 18:10

Nate Potter


2 Answers

The parent entity can set itself as an observer of the relationship and it will get notified when that relationship changes. However that will only be fired when the actual relationship (adding or removing a child) occurs.

To watch for a specific child entity is far more tricky. There are a couple of ways to go about it:

  1. Have the child ping the parent when its properties change.
  2. Have the parent listen for NSManagedObjectContextDidSaveNotification and look to see if any of its children are in that save
  3. Have the parent observe the values on the children.

There may be other solutions but of the three I recommend #2. It is pretty easy to set up and the performance impact is pretty minimal.

like image 147
Marcus S. Zarra Avatar answered Nov 14 '22 10:11

Marcus S. Zarra


In the other answer I found 1,2 and 3 too inefficient. Particularly 2 and the example in the "Parent Watching it's Child" blog post. My issue with that is every single parent had to respond to the context notification and essentially every object being saved if it is the child (never mind the fact ContextDidSave is more appropriate in that case!). Instead, I would propose an option 4:

  1. Have the child override didSave and broadcast a NSManagedObjectContextDidSaveNotification containing the parent.

My solution is more efficient and feels more object-oriented to me since the object that is changing is responding to its own change. To implement this, within the child object use:

-(void)didSave{
    [super didSave];
    // notify that the parent has changed.
    [[NSNotificationCenter defaultCenter] postNotificationName:NSManagedObjectContextObjectsDidChangeNotification
                                                        object:self.managedObjectContext
                                                      userInfo:@{NSUpdatedObjectsKey : [NSSet setWithObject:self.parent]}];;
}

To update the parent timestamp, the following neat solution (second last post) I've been using might help, e.g. in the parent use:

- (void) awakeFromInsert
{
    [super awakeFromInsert];
    // set the default dates
    NSDate* date = [NSDate date];
    self.timestamp = date;
    //allow any future modifications to change the timestamp
    _finishedStartup = YES;
}

- (void) awakeFromFetch
{
    [super awakeFromFetch];
    // we should already have creation and modified dates.
    _finishedStartup = YES;
}

- (void) didChangeValueForKey: (NSString *) thisKey
{
    [super didChangeValueForKey: thisKey];

    if(![thisKey isEqualToString:@"timestamp"] && // prevent infinite loop
       ![self isFault] &&
       ![[[self managedObjectContext] undoManager] isUndoing] &&
       _finishedStartup) // ensures we arent being called by the object being loaded from fetched data.
    {
        self.timestamp = [NSDate date];
    }
}
like image 2
malhal Avatar answered Nov 14 '22 12:11

malhal