I'm seeing some quirky behaviour with Cocoa's KVC/KVO and bindings. I have an NSArrayController
object, with its 'content' bound to an NSMutableArray
, and I have a controller registered as an observer of the arrangedObjects
property on the NSArrayController
. With this setup, I expect to receive a KVO notification every time the array is modified. However, it appears that the KVO notification is only sent once; the very first time the array is modified.
I set up a brand new "Cocoa Application" project in Xcode to illustrate the problem. Here is my code:
BindingTesterAppDelegate.h
#import <Cocoa/Cocoa.h>
@interface BindingTesterAppDelegate : NSObject <NSApplicationDelegate>
{
NSWindow * window;
NSArrayController * arrayController;
NSMutableArray * mutableArray;
}
@property (assign) IBOutlet NSWindow * window;
@property (retain) NSArrayController * arrayController;
@property (retain) NSMutableArray * mutableArray;
- (void)changeArray:(id)sender;
@end
BindingTesterAppDelegate.m
#import "BindingTesterAppDelegate.h"
@implementation BindingTesterAppDelegate
@synthesize window;
@synthesize arrayController;
@synthesize mutableArray;
- (void)applicationDidFinishLaunching:(NSNotification *)notification
{
NSLog(@"load");
// create the array controller and the mutable array:
[self setArrayController:[[[NSArrayController alloc] init] autorelease]];
[self setMutableArray:[NSMutableArray arrayWithCapacity:0]];
// bind the arrayController to the array
[arrayController bind:@"content" // see update
toObject:self
withKeyPath:@"mutableArray"
options:0];
// set up an observer for arrangedObjects
[arrayController addObserver:self
forKeyPath:@"arrangedObjects"
options:0
context:nil];
// add a button to trigger events
NSButton * button = [[NSButton alloc]
initWithFrame:NSMakeRect(10, 10, 100, 30)];
[[window contentView] addSubview:button];
[button setTitle:@"change array"];
[button setTarget:self];
[button setAction:@selector(changeArray:)];
[button release];
NSLog(@"run");
}
- (void)changeArray:(id)sender
{
// modify the array (being sure to post KVO notifications):
[self willChangeValueForKey:@"mutableArray"];
[mutableArray addObject:[NSString stringWithString:@"something"]];
NSLog(@"changed the array: count = %d", [mutableArray count]);
[self didChangeValueForKey:@"mutableArray"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
NSLog(@"%@ changed!", keyPath);
}
- (void)applicationWillTerminate:(NSNotification *)notification
{
NSLog(@"stop");
[self setMutableArray:nil];
[self setArrayController:nil];
NSLog(@"done");
}
@end
And here is the output:
load
run
changed the array: count = 1
arrangedObjects changed!
changed the array: count = 2
changed the array: count = 3
changed the array: count = 4
changed the array: count = 5
stop
arrangedObjects changed!
done
As you can see, the KVO notification is only sent the first time (and once more when the application exits). Why would this be the case?
update:
Thanks to orque for pointing out that I should be binding to the contentArray
of my NSArrayController
, not just its content
. The above posted code works, as soon as this change is made:
// bind the arrayController to the array
[arrayController bind:@"contentArray" // <-- the change was made here
toObject:self
withKeyPath:@"mutableArray"
options:0];