How to observe a collection of objects and changes to their properties

The easy way is to use an NSArrayController and add an observer like so:

[friendsArrayController addObserver:self forKeyPath:@"arrangedObjects.name" options:0 context:nil];

The hard way is to observe a model’s collection property and add/remove observers when objects get added/removed from the collection like so:

[person addObserver:self forKeyPath:@"friends" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if (object == person && [keyPath isEqualTo:@"friends"]) {
            if ([[change valueForKey:NSKeyValueChangeKindKey] intValue] == NSKeyValueChangeInsertion) {
                for (Friend *newFriend in [change valueForKey:NSKeyValueChangeNewKey]) {
                    if (![observedFriends containsObject:newFriend]) {
                        [newFriend addObserver:self forKeyPath:@"name" options:0 context:nil];
                    }
                }
            }
            else if ([[change valueForKey:NSKeyValueChangeKindKey] intValue] == NSKeyValueChangeRemoval) {
                for (Friend *oldFriend in [change valueForKey:NSKeyValueChangeOldKey]) {
                    if (![observedFriends containsObject:oldFriend]) {
                        [oldFriend removeObserver:self forKeyPath:@"name"];
                    }
                }
            }
            else if ([[change valueForKey:NSKeyValueChangeKindKey] intValue] == NSKeyValueChangeReplacement) {
                for (Friend *newFriend in [change valueForKey:NSKeyValueChangeNewKey]) {
                    if (![observedFriends containsObject:newFriend]) {
                        [newFriend addObserver:self forKeyPath:@"name" options:0 context:nil];
                    }
                }
                for (Friend *oldFriend in [change valueForKey:NSKeyValueChangeOldKey]) {
                    if (![observedFriends containsObject:oldFriend]) {
                        [oldFriend removeObserver:self forKeyPath:@"name"];
                    }
                }
            }
    }
}