3

I have a NSMutableArray property in my AppDelegate called blocks. I would like to observe whenever an object is added to this array. I've read other posts, but I can't understand why this isn't working.

In my app delegate class, I implement

- (void)insertObject:(id)obj inBlocksAtIndex:(NSInteger)index
{
    [blocks insertObject:obj atIndex:index];
}

In my view controller's init method, I add an observer to my AppDelegate reference.

boardModel = [[UIApplication sharedApplication] delegate];
[boardModel addObserver:self forKeyPath:@"blocks" options:0 context:NULL];

In my view controller's viewDidLoad method, I try invoking the KVO Indexed array accessor I implemented previously,

[boardModel insertObject:[[Block alloc] init] inBlocksAtIndex:0];

Then I implement my observeValueForKeyPath method:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"blocks"])
    {
        NSLog(@"ADDED");
    }
}

I've tried adding an NSLog statement before the if statement in observeValueForKeyPath, and it seems as if it's never being called.

I've also tried NSLogging [[boardModel blocks] count], and it says the count is 1 (the object is being added).

I must be missing something.

Dalisto1
  • 372
  • 3
  • 10

5 Answers5

12

The catch is that NSArrays don't respect KVO, so observing the key path count won't work.

If this is MacOSX, use NSArrayController. otherwise implement a wrapper class for the array that triggers the KVO calls when adding/removing contents of the array, and passes across all other calls.

Paul Lynch
  • 19,769
  • 4
  • 37
  • 41
1

Did you try

   - (void)insertObject:(id)obj inBlocksAtIndex:(NSInteger)index
    {
        [[self mutableArrayValueForKey:@"blocks"] insertObject:obj atIndex:index];
     }
Simon
  • 577
  • 3
  • 9
1

You're observing the blocks property of the app delegate, not the blocks array itself. Hopefully the following example will make the difference clear:

// This will fire KVO as you're changing the app delegate's `blocks` property.
appDelegate.blocks = [NSMutableArray array];

// This will not fire KVO as the app delegate's `blocks` property still points
// to the same object; from the app delegate's perspective, nothing's happened.
[appDelegate.blocks addObject:@"Object"];

If you want to be notified when the contents of the blocks array changes, observe a property on the array itself—something like count. Updating your code:

[boardModel.blocks addObserver:self forKeyPath:@"count" options:0 context:NULL];
0

I just open-sourced a very small Objective-C library that adds a delegate to NSMutableArray. It might help you achieve what you were trying to do. Check out FCMutableArray on GitHub

P. Unto
  • 236
  • 1
  • 11
0

It's not that NSMutableArray is not KVO-compliant in some respects, it's that all KVO-compliant properties must be NSObjects. "count" is an NSUInteger; you can't observe that property directly. If it was an NSNumber object, you could.

From the NSArray header file:

@interface NSArray<__covariant ObjectType> : NSObject <NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration>

@property (readonly) NSUInteger count;

Why was the answer accepted, by the way? It's clear the answerer did not test his answer.

James Bush
  • 1,485
  • 14
  • 19