Key-Value-Observing a to-many relationship in Cocoa

11,849

Solution 1

You need to implement the indexed array accessors as defined in the KVC programming guide. Then you must use those accessors to access the array and the KVO triggering will work. You can also call -mutableArrayValueForKey: and use that array to addObject: and such and it will in turn call the accessor methods and the KVO triggering will occur as well. There are also set accessors for use in for NSSets, see here and here.

Example:

@interface MyClass : NSObject
{
    NSMutableArray *_orders;
}

@property(retain) NSMutableArray *orders;

- (NSUInteger)countOfOrders;
- (id)objectInOrdersAtIndex:(NSUInteger)index;
- (void)insertObject:(id)obj inOrdersAtIndex:(NSUInteger)index;
- (void)removeObjectFromOrdersAtIndex:(NSUInteger)index;
- (void)replaceObjectInOrdersAtIndex:(NSUInteger)index withObject:(id)obj;


@end

Solution 2

Unfortunately, The NSArray classes are note KVO compliant. They are KVC compliant, but you can't observe them directly like you're trying to do here. The easiest way to get this functionality would be to use an NSArrayController. The NSArray controller is KVO compliant and will alert you when items are added or removed. In your example, your observer would be notified if you actually changed the array itself. For instance, if you did something like this:

[moe setSomeArray:[NSMutableArray array]];

Which is probably not what you wanted at all :) Just as an aside, NSDictionary is actually KVO compliant so you could use that, if you chose. Or you could write a wrapper subclass of NSMutableArray that just creates a real mutable array as its backing store but just forwards on all messages to it except addObject and removeObject which you could override to trigger notifications.

Solution 3

Why are you passing your private array to another object? It's not so private when you let other objects handle it.

As s-bug said, you should implement the accessors and use mutableArrayValueForKey: to mutate the property. I add that you should not be exposing that private array at all—your someArray method should return an immutable copy of the array.

Furthermore, I call your attention to Jason Coco's comment on s-bug's answer. To paraphrase him, you should probably use an NSArrayController as an additional step of separation between myObservee and myObserver. This is a very good suggestion, and if you don't have a specific reason to directly observe the property, you should take it. (Among the benefits is that you can then use Bindings to connect views to the new array controller.)

Share:
11,849
Sam Lee
Author by

Sam Lee

Updated on July 29, 2022

Comments

  • Sam Lee
    Sam Lee almost 2 years

    I am trying to get key-value-observing to work for an NSMutableArray. Below is the .h file for MyObservee, the observed class:

    @interface MyObservee : NSObject {
        @private int someValue;
        @private NSMutableArray *someArray;
    }
    
    @property (readwrite,assign) int someValue;
    - (NSMutableArray *)someArray;
    @end
    

    The class MyObserver implements observeValueForKeyPath:ofObject:change:context:. Here is how I add the observer:

    MyObservee *moe = [[MyObservee alloc] init];
    MyObserver *mobs = [[MyObserver alloc] init];
    
    [moe addObserver:mobs 
          forKeyPath:@"someArray" 
             options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) 
             context:NULL];
    
    [moe.someArray addObject:@"hi there"];
    

    How come the addObject: message isn't triggering as a change to the someArray key path? I have a feeling there's something I don't fully understand here.

  • Jason Coco
    Jason Coco over 15 years
    or... you could just use an NSArrayController ;-)
  • Nico
    Nico over 15 years
    Jason Coco: Yes, one generally should use an NSArrayController, but that doesn't solve the problem of how to mutate the underlying model and get KVO notifications.