可可结合和KVO

时间:2011-04-16 21:12:28

标签: cocoa cocoa-bindings key-value-observing

我有一个视图MyView,它有我希望与AppDelegate中的数组绑定的图像。

MyView上课

@interface MyView : NSView {
@private
    NSArray *images;
}

@end

+ (void)initialize
{
    [self exposeBinding:@"images"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    NSLog(@"Changed!");
}

我的AppDelegate

@property (retain) NSArray *images;

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{   
    images = [[NSMutableArray alloc] init];

    [view bind:@"images" toObject:self withKeyPath:@"images" options:nil];
    // [self addObserver:view forKeyPath:@"images" options:0 context:nil]; // !!!

    MyImage *img = [[MyImage alloc] ...];

    [self willChangeValueForKey:@"images"];
    [[self images] addObject:img];
    [self didChangeValueForKey:@"images"];
    [img release];
}

如果没有[self addObserver:view forKeyPath:@"images" options:0 context:nil];,则永远不会调用方法observeValueForKeyPath:

使用addObserver:时是否需要致电bind:bind:是否设置了KVO?为什么没有约束力呢?

2 个答案:

答案 0 :(得分:3)

您需要的是图像属性的实现设置器,如下所示。最常见的用例是您需要使绘图无效并请求重绘 -setNeedsDisplay:YES

- (void)setImages:(NSArray *)newImages
{
  if(newImages != images) {
    [images release];
    images = newImages;
    [images retain];
  }

  [self setNeedsDisplay:YES]; // Addition and only difference to synthesized setter
}

您可以放弃-exposeBinding:调用,因为这只会影响Interface Builder的插件,以及因Xcode 4引入而丢失的插件。

未发送-observeValueForKeyPath:ofObject:change:context:消息的原因是对于绑定,观察者不是绑定对象。背景中还有另一个对象。 (在堆栈中形成一个断点,你可以看到它的类是NSEditableBinder。)因此,从视图中注册为观察者到视图属性@“images”是正确的。

另一种获得视图更改通知的方法是覆盖-setValue:forKey:方法。然后,您需要检查密钥字符串,看它是否等于@"images"。但由于KVC协议中还有其他方法,如-setValue:forKeyPath:,您需要格外小心,不要打扰机器,即始终呼叫super

嗯。我只是意识到我的答案到目前为止假设你更换整个数组的更简单的情况。你的问题是数组修改。 (你确实在你的例子中声明了一个不可变的数组属性,它只允许替换。所以保持它的声明,到目前为止我的方法将起作用。下面我展示另一种选择。)

好的,我们假设你在app delegate中做了这个,替换:

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{   
    [view bind:@"images" toObject:self withKeyPath:@"images" options:nil];

    MyImage *img = [[MyImage alloc] ...];

    self.images = [NSArray arrayWithObject:img];
    [img release];
}

您不需要发布更改(使用willChangeValueForKey:didChangeValueForKey:,因为您通过声明的属性。他们会为您执行此操作。

现在转到修改数组的另一种方法。为此,您需要使用可变数组属性并通过KVO通知代理修改它,如下所示:

[self mutableArrayValueForKey:@"images"] addObject:img];

这将获取发送(绑定)方面的更改。然后它将通过绑定机器传输到视图,并最终使用KVC进行设置。

在视图的接收端,您需要将属性更改为@“images”。这可以通过覆盖集合访问器方法并在那里做更多工作来完成,而不是仅仅接受更改。但这有点复杂,因为有很多访问方法(参见docs)。或者,更简单,您可以在视图中添加另一个观察关系。

为此,在视图的初始化(例如-awakeFromNib:)中的某处:

[self addObserver:self forKeyPath:@"images" options:0 context:nil];

然后:

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
  [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];

  if([keyPath isEqualToString:@"images"]) {
    [self setNeedsDisplay:YES]; // or what else you need to do then.
  }
}

请注意,此最后一个观察者关系不再与绑定无关。对绑定属性的值更改正确地到达视图而没有,您只是没有意识到(得到通知)。

这应该有效。

答案 1 :(得分:0)

调用observeValueForKeyPath的唯一方法是致电addObserver。绑定通过不同的机制起作用。