1

I want to observe a NSMutableArray in my class cSoundChannel. Hence after reading this post Observing an NSMutableArray for insertion/removal I implemented the key observing in this manner.

For cSoundChanel class,

My property for the mutable array is

 @property (assign, nonatomic) NSMutableArray* midiDevices;

The functions I introduced using kvo array accessors in the class are as follows :

- (void) addmidiDevicesObject:(NSObject *) str {
    [self insertObject:str inMidiDevicesAtIndex:[_midiDevices count]];
}

- (void)insertObject:(NSObject *)str inMidiDevicesAtIndex:(NSUInteger)index {
    [self.midiDevices insertObject:str atIndex:index];
    return;
}

For my ViewController.m file, where I need to observe midiDevices, I did the following.

[self.cSoundChannel addObserver:self forKeyPath:@"midiDevices" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];

and expected to be able to observe the mutable array in ...

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:@"midiDevices"]) {
        NSLog(@"Let's see!");
    }
}

but alas... it did not print "Let's see!" Observing other things... NSString etc works...

Is there anything that I missed out? Help!

Community
  • 1
  • 1
lppier
  • 1,927
  • 3
  • 24
  • 62

2 Answers2

2

Your code is not fully KVC compliant on midiDevices. You also need to implement removeObjectFromMidiDevicesAtIndex::

- (void)removeObjectFromMidiDevicesAtIndex:(NSUInteger)index {
  [self.midiDevices removeObjectAtIndex:index];
}

See the Key-Value Coding Programming Guide for full details. Specifically see Mutable Indexed Accessors.


EDIT: The following example code demonstrates what I'm describing, and prints "Let's see!" as expected. Removing removeObjectFromMidiDevicesAtIndex: will re-introduce the bug seen in the OP's code.

#import <Foundation/Foundation.h>

@interface Observed : NSObject
@property (nonatomic) NSMutableArray* midiDevices;
@end

@implementation Observed

- (id)init {
  self = [super init];
  if (self) {
    _midiDevices = [NSMutableArray new];
  }
  return self;
}

- (void) addmidiDevicesObject:(NSObject *) str {
  [self insertObject:str inMidiDevicesAtIndex:[_midiDevices count]];
}

- (void)insertObject:(NSObject *)str inMidiDevicesAtIndex:(NSUInteger)index {
  [self.midiDevices insertObject:str atIndex:index];
}

- (void)removeObjectFromMidiDevicesAtIndex:(NSUInteger)index {
  [self.midiDevices removeObjectAtIndex:index];
}

@end

@interface Observer : NSObject
@property (nonatomic) Observed *observed;
@end

@implementation Observer

- (id)init {
  self = [super init];
  if (self) {
    _observed = [Observed new];
    [_observed addObserver:self forKeyPath:@"midiDevices" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
  }
  return self;
}

- (void)dealloc {
  [_observed removeObserver:self forKeyPath:@"midiDevices"];
}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
  if ([keyPath isEqualToString:@"midiDevices"]) {
    NSLog(@"Let's see!");
  }
}

@end

int main(int argc, const char * argv[]) {
  @autoreleasepool {
    Observer *observer = [Observer new];
    [observer.observed addmidiDevicesObject:@"test"];
  }
  return 0;
}
Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • That's not the problem here and not an answer. Change it to comment maybe to his code as tip. – Grzegorz Krukowski Sep 13 '13 at 11:30
  • 2
    It is the problem here, and by making it KVC compliant the observer will be called. – Rob Napier Sep 13 '13 at 13:18
  • Wonderful. All I needed was to add - (void)removeObjectFromMidiDevicesAtIndex:(NSUInteger)index as you said. Thank you! – lppier Sep 15 '13 at 04:23
  • I have a new problem here. In the cases where there are more than 1 item in the mididevices nsmutablearray in the class being observed, I only see 1 object when I do NSMutableArray *midiDevicesArray = [change objectForKey:NSKeyValueChangeNewKey]; Why is this? – lppier Sep 15 '13 at 05:11
  • I realised that for some reason the observed value only shows the change in the NSMutableArray, and for 2 items added to the array being observed, it is called 2 times. – lppier Sep 15 '13 at 05:30
  • Is there any way for the NSMutableArray that I observe to be the entire NSMutableArray? – lppier Sep 15 '13 at 05:31
  • I don't understand what you mean. If you call `insertObject: inMidiDevicesAtIndex:` you're going to get a single object inserted. `NSKeyValueChangeKindKey` should be `NSKeyValueChangeInsertion`, `...NewKey` will be a single-element array (since you inserted one object), and `...IndexesKey` will be an index set with a single element. The whole dictionary is documented in the `NSKeyValueObserving` docs. – Rob Napier Sep 15 '13 at 19:58
1

Just having an NSMutableArray will not get you the ability to watch changes. You need to create a mutable array with the NSKeyValueCoding protocol - "mutableArrayValueForKey". This array is a "special" array that will fire off KVO messages whenever the contents of the array changes.

You need two arrays here. One which is my "real" data, in your case, midiDevices, then the second array, created this way:

mutableMidiDevices = [self mutableArrayValueForKey:@"midiDevices"];

From here, you only add/remove objects to your "mutableMidiDevices" and don't access the original "midiDevices" at all (except for the KVO routines that you need to define(.

Here are your KVO routines:

- (NSUInteger) countOfMidiDevices;
- (id) objectInMidiDevicesAtIndex:(NSUInteger)index;
- (void) replaceObjectInMidiDevicesAtIndex:(NSUInteger)index withObject:(id)object;
- (void) insertObject:(id)midiDevice inMidiDevicesAtIndex:(NSUInteger)index;
- (void) removeObjectFromMidiDevicesAtIndex:(NSUInteger)index;

I'm not sure right now if you need to define all of these routines, but that's what I did last time I did KVO. Each of these routines should be modifying the original midiDevices array.

So, the idea is that you add/delete to "mutableMidiDevices" from outside & inside your object. This will guarantee that KVO messages fire.

From inside your object, you modify the real "midiDevices" array by defining the KVO routines.

Hope this helps.

Ekta Padaliya
  • 5,743
  • 3
  • 39
  • 51
Dan Morrow
  • 4,433
  • 2
  • 31
  • 45