2

我看到 Cocoa 的 KVC/KVO 和绑定有一些古怪的行为。我有一个NSArrayController对象,它的“内容”绑定到NSMutableArray,并且我有一个控制器注册arrangedObjectsNSArrayController. 使用此设置,我希望每次修改阵列时都会收到 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指出我应该绑定到contentArraymy 的NSArrayController,而不仅仅是它的content。只要进行此更改,上面发布的代码就可以工作:

// bind the arrayController to the array
[arrayController bind:@"contentArray" // <-- the change was made here
             toObject:self
          withKeyPath:@"mutableArray"
              options:0];
4

3 回答 3

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],您可以在排列对象上更改/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"];
}

可能有一个更便宜的密钥会产生相同的效果。如果您有选择,我建议您只通过控制器工作并将通知留给底层系统。

于 2009-08-21T20:51:55.593 回答
5

比显式发布全值 KVO 通知更好的方法是实现数组访问器并使用它们。然后 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:它是,因此您对前者的实现(如果您选择这样做)必须使用后者。

于 2009-08-21T23:17:03.223 回答
0

哦,我一直在寻找这个解决方案!谢谢大家 !在得到想法并玩弄之后,我发现了另一种非常奇特的方式:

假设我有一个像这样的对象 CubeFrames:

@interface CubeFrames : NSObject {
NSInteger   number;
NSInteger   loops;
}

我的数组包含 Cubeframes 的对象,它们由 objectController 通过 (MVC) 管理并显示在 tableView 中。绑定以常用方式完成:objectController 的“内容数组”绑定到我的数组。重要提示:将 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);
}

现在,确实,我捕捉到了所有的变化:添加和删除行,改变循环或数字:-)

于 2016-02-02T11:23:56.783 回答