Search code examples
objective-ccocoakey-value-observing

How do you signal an update to a readonly @property which is dependant on children properties?


This is a simplified example that should be enough for the question.

@interface MyClass: NSObject {
   Person *_owner;
}
   @property (strong) Person *owner;
   @property (readonly) BOOL hasSomething;
@end

@implementation
   // other code here like -init
   - (void)setOwner:(Person *)newOwner
   {
       [_owner removeObserver:self forKeyPath:@"stuff"];
       _owner = newOwner;
       [_owner addObserver:self forKeyPath:@"stuff" options:0 context:NULL];
   }
   - (Person*)owner
   {
       return _owner;
   }
   - (BOOL)hasSomething
   {
       return owner.stuff > 0;
   }
   - (void)observeValueForKeyPath:(NSString*)kp ofObject:(id)obj change:(NSDictionary*)ch context:(void*)c
   {
       if ([kp isEqualtToString:@"stuff"]) {
           [self willChangeValueForKey:@"hasSomething"];
           [self didChangeValueForKey:@"hasSomething"];
       }
   }
@end

In my example, anything bound to hasSomething is not properly notified of a change of value. What am I missing?


Solution

  • My workaround: I initially worked around the issue by binding straight to the value that I wanted to track. In the example given it would have looked like arrangedObjects.owner.stuff.

    My Solution: I later revisited the code and discovered that what I had was essentially correct. The idea was to encapsulate the inner working of a hierarchy of objects (which isn't achieve if you bind the whole path like my workaround above). In the question, I simplified the problem to a hierarchy with two levels : MyClass -> Person. My code actually involves more levels and a to-many relationship. One of the elements in the hierarchy wasn't properly observing its children.


    Denis suggested I take a look at -automaticallyNotifiesObserversForKey: and I did. From my understanding, you would want to have the method return NO in the event you want to send the notification manually within the setter method of the property. Otherwise, your notifications conflict with the automatic ones.

    - (void)setHasSomething:(BOOL)newVal
    {
        [self willChangeValueForKey:@"hasSomething"];
        // ...
        [self didChangeValueForKey:@"hasSomething"];
    }
    

    Since there's no setter method for a readonly property, having it didn't solve my problem.


    Lessons learned: 1) Observe your children properly; 2) Don't over-simplify your problem when asking for help.