Observing an NSMutableArray for insertion/removal

Viewed 35375

A class has a property (and instance var) of type NSMutableArray with synthesized accessors (via @property). If you observe this array using:

[myObj addObserver:self forKeyPath:@"theArray" options:0 context:NULL];

And then insert an object in the array like this:

[myObj.theArray addObject:NSString.string];

An observeValueForKeyPath... notification is not sent. However, the following does send the proper notification:

[[myObj mutableArrayValueForKey:@"theArray"] addObject:NSString.string];

This is because mutableArrayValueForKey returns a proxy object that takes care of notifying observers.

But shouldn't the synthesized accessors automatically return such a proxy object? What's the proper way to work around this--should I write a custom accessor that just invokes [super mutableArrayValueForKey...]?

7 Answers

But shouldn't the synthesized accessors automatically return such a proxy object?

No.

What's the proper way to work around this--should I write a custom accessor that just invokes [super mutableArrayValueForKey...]?

No. Implement the array accessors. When you call these, KVO will post the appropriate notifications automatically. So all you have to do is:

[myObject insertObject:newObject inTheArrayAtIndex:[myObject countOfTheArray]];

and the Right Thing will happen automatically.

For convenience, you can write an addTheArrayObject: accessor. This accessor would call one of the real array accessors described above:

- (void) addTheArrayObject:(NSObject *) newObject {
    [self insertObject:newObject inTheArrayAtIndex:[self countOfTheArray]];
}

(You can and should fill in the proper class for the objects in the array, in place of NSObject.)

Then, instead of [myObject insertObject:…], you write [myObject addTheArrayObject:newObject].

Sadly, add<Key>Object: and its counterpart remove<Key>Object: are, last I checked, only recognized by KVO for set (as in NSSet) properties, not array properties, so you don't get free KVO notifications with them unless you implement them on top of accessors it does recognize. I filed a bug about this: x-radar://problem/6407437

I have a list of all the accessor selector formats on my blog.

I would not use willChangeValueForKey and didChangeValueForKey in this situation. For one, they're meant to indicate the value at that path has changed, not that values in a to-many relationship are changing. You would want to use willChange:valuesAtIndexes:forKey: instead, if you did it this way. Even so, using manual KVO notifications like this is bad encapsulation. A better way of doing it is defining a method addSomeObject: in the class that actually owns the array, which would include the manual KVO notifications. This way, outside methods that are adding objects to the array don't need to worry about handling the array owner's KVO as well, which wouldn't be very intuitive and could lead to unneeded code and possibly bugs if you start adding objects to the array from several places.

In this example I would actually continue to use mutableArrayValueForKey:. I'm not positive with mutable arrays, but I believe from reading the documentation that this method actually replaces the entire array with a new object, so if performance is a concern you'll also want to implement insertObject:in<Key>AtIndex: and removeObjectFrom<Key>AtIndex: in the class that owns the array.

You need to wrap your addObject: call in willChangeValueForKey: and didChangeValueForKey: calls. As far as I know, there's no way for the NSMutableArray you're modifiying to know about any observers watching its owner.

Related