我看到Cocoa的KVC / KVO和绑定有些古怪的行为。我有一个NSArrayController
对象,其'content'绑定到NSMutableArray
,我有一个控制器注册为arrangedObjects
NSArrayController
属性的观察者。通过此设置,我希望每次修改阵列时都会收到KVO通知。但是,似乎KVO通知仅发送一次;第一次修改数组。
我在Xcode中设置了一个全新的“Cocoa Application”项目来说明问题。这是我的代码:
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
这是输出:
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
如您所见,KVO通知仅在第一次发送时(并在应用程序退出时再次发送)。为什么会这样呢?
更新
感谢orque指出我应该绑定到contentArray
的{{1}},而不仅仅是NSArrayController
。只要进行了此更改,上面发布的代码就会起作用:
content
答案 0 :(得分:7)
首先,您应该绑定到contentArray(而不是内容):
[arrayController bind:@"contentArray"
toObject:self
withKeyPath:@"mutableArray"
options:0];
然后,直接的方法是使用arrayController来修改数组:
- (void)changeArray:(id)sender
{
// modify the array (being sure to post KVO notifications):
[arrayController addObject:@"something"];
NSLog(@"changed the array: count = %d", [mutableArray count]);
}
(在实际场景中,您可能只希望按钮操作调用-addObject:)
使用 - [NSMutableArray addObject]不会自动通知控制器。我看到你试图通过在mutableArray上手动使用willChange / didChange来解决这个问题。这不起作用,因为数组本身没有改变。也就是说,如果KVO系统在更改之前和之后查询mutableArray,它仍将具有相同的地址。
如果你想使用 - [NSMutableArray addObject],你可以在arrangeObjects上更改/ didChange:
- (void)changeArray:(id)sender
{
// modify the array (being sure to post KVO notifications):
[arrayController willChangeValueForKey:@"arrangedObjects"];
[mutableArray addObject:@"something"];
NSLog(@"changed the array: count = %d", [mutableArray count]);
[arrayController didChangeValueForKey:@"arrangedObjects"];
}
可能会有一个更便宜的钥匙,可以产生同样的效果。如果您有选择,我建议您只使用控制器并将通知留给底层系统。
答案 1 :(得分:5)
比明确发布整值KVO通知更好的方法是实现array accessors并使用它们。然后KVO免费发布通知。
那样,而不是:
[self willChangeValueForKey:@"things"];
[_things addObject:[NSString stringWithString:@"something"]];
[self didChangeValueForKey:@"things"];
你会这样做:
[self insertObject:[NSString stringWithString:@"something"] inThingsAtIndex:[self countOfThings]];
KVO不仅会为您发布更改通知,而且它将是一个更具体的通知,是数组插入更改而不是整个数组更改。
我通常添加一个执行上述操作的addThingsObject:
方法,以便我可以这样做:
[self addThingsObject:[NSString stringWithString:@"something"]];
请注意add<Key>Object:
目前不是KVC认可的数组属性选择器格式(仅设置属性),而insertObject:in<Key>AtIndex:
是,所以你的实现前者(如果你选择这样做) 必须使用后者。
答案 2 :(得分:0)
哦,我正在寻找这个解决方案很长一段时间!谢谢大家 ! 在得到这个想法之后在玩耍时,我发现了另一种非常奇特的方式:
假设我有一个像这样的对象CubeFrame:
@interface CubeFrames : NSObject {
NSInteger number;
NSInteger loops;
}
My Array包含Cubeframes的对象,它们由objectController通过(MVC)管理并显示在tableView中。 绑定是以常见的方式完成的: &#34;内容数组&#34; objectController绑定到我的数组。 重要:设置&#34;班级名称&#34; objectController的类CubeFrames
如果我在Appdelegate中添加这样的观察者:
-(void)awakeFromNib {
//
// register ovbserver for array changes :
// the observer will observe each item of the array when it changes:
// + adding a cubFrames object
// + deleting a cubFrames object
// + changing values of loops or number in the tableview
[dataArrayCtrl addObserver:self forKeyPath:@"arrangedObjects.loops" options:0 context:nil];
[dataArrayCtrl addObserver:self forKeyPath:@"arrangedObjects.number" options:0 context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
NSLog(@"%@ changed!", keyPath);
}
现在,确实,我抓住了所有的变化:添加和删除行,更改循环或数字: - )