CoreData undo and observing related objects

  • I'm using observing to update attributes based on changes in related
    objects.

    I do this by implementing accessor methods:

    // objA watches objB for keyPath

    @implementation ObjA
    - (void) setObjB:(ObjB *)newValue
    {
    id oldValue = [self objB];

    if (oldValue)
      [oldValue removeObserver:self keyPath:keyPath];

    [self willChangeValueForKey:@"objB"];
    [self setPrimitiveValue:newValue forKey:@"objB"];
    [self didChangeValueForKey:@"objB"];

    if (newValue)
      [newValue addObserver:self forKeyPath:keyPath options:0 context:NULL];
    }

    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)
    object change:(NSDictionary *)change context:(void *)context
    {
    // Do something
    }
    @end

    This works fine when I change the value through code or GUI, but when
    NSUndoManager does it's work the accessor methods aren't called do my
    observing isn't updated.

    So my next attempt was to have objA observe it's own property objB,
    but that does not seem to be trigged by undo either:

    @implementation ObjB
    - (void) startObservingSelf
    {
    [newValue addObserver:self forKeyPath:@"objB" options:
    (NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
    context:NULL];
    }
    - (void) awakeFromFetch
    {
    [self startObservingSelf];
    }
    - (void) awakeFromInsert
    {
    [self startObservingSelf];
    }
    - (void)observeValueForKeyPath:(NSString *)key ofObject:(id)object
    change:(NSDictionary *)change context:(void *)context
    {
    if (object == self)
    {
      // stop observing the old value
      // start observing the new value
    }
    else if ([key isEqualToString:keyPath])
      // do something
    ...
    }
    @end

    I would have expected that undo would trigger the observation, but it
    seems it does not. Should it?

    How is this usually done?

    Martin Wennerberg
    Norrkross Software
  • There was another error in my code that made the observation fail. So
    observing self actually works fine, as expected.

    For completeness, here's a short example program of how to update
    observation of related core data objects.

    In this example a Parent object has a relation to a Child. When the
    child's value changes the parent updates it's own value to the same.
    (In a real world it would have to do something more interesting with
    it, or else it would be better to simply access the child value via
    the parent.)

    Here's the complete code for the Parent class:

    @interface Parent :  NSManagedObject
    {
    BOOL isObseringSelf;
    }
    @end

    @implementation Parent
    - (void) startObservingMyChildProperty
    {
        [self addObserver:self forKeyPath:@"child" options:
    (NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
    context:NULL];

        Child  *child = [self valueForKey:@"child"];
        if (child)
        {
            [self setValue:[child valueForKey:@"value"] forKey:@"value"];
            [child addObserver:self forKeyPath:@"value" options:0
    context:NULL];
        }
        isObservingSelf = YES;
    }

    - (void) stopObservingMyChildProperty
    {
        if (isObservingSelf)
        {
            [self removeObserver:self forKeyPath:@"child"];

            Child  *child = [self valueForKey:@"child"];
            if (child)
                [child removeObserver:self forKeyPath:@"value"];

            isObservingSelf = NO;
        }
    }

    - (void) dealloc
    {
        [self stopObservingMyChildProperty];
        [super dealloc];
    }

    - (void)didTurnIntoFault
    {
        [self stopObservingMyChildProperty];
        [super didTurnIntoFault];
    }

    - (void) awakeFromInsert
    {
        [super awakeFromInsert];
        [self startObservingMyChildProperty];
    }

    - (void) awakeFromFetch
    {
        [super awakeFromFetch];
        [self startObservingMyChildProperty];
    }

    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)
    object change:(NSDictionary *)change context:(void *)context
    {
        if ([object isEqual:[self valueForKey:@"child"]])
            [self setValue:[object valueForKey:@"value"] forKey:@"value"];
        else if (object == self)
        {
            Child  *oldChild = [change
    objectForKey:NSKeyValueChangeOldKey];
            Child  *newChild = [change
    objectForKey:NSKeyValueChangeNewKey];

            if (oldChild && ![oldChild isEqual:[NSNull null]])
                [oldChild removeObserver:self forKeyPath:@"value"];
            if (newChild && ![newChild isEqual:[NSNull null]])
            {
                [newChild addObserver:self forKeyPath:@"value" options:0
    context:NULL];
                [self setValue:[newChild valueForKey:@"value"]
    forKey:@"value"];
            }
        }
        else
            NSLog (@"Unexpected %@", NSStringFromSelector(_cmd));
    }
    @end

    Cheers,
    Martin Wennerberg