Cocoa绑定和KVO

Cocoa绑定和KVO,cocoa,cocoa-bindings,key-value-observing,Cocoa,Cocoa Bindings,Key Value Observing,我有一个视图MyView,它有一些图像,我想用我的AppDelegate中的数组绑定它们 MyViewclass @interface MyView : NSView { @private NSArray *images; } @end + (void)initialize { [self exposeBinding:@"images"]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)o

我有一个视图
MyView
,它有一些图像,我想用我的
AppDelegate
中的数组绑定它们

MyView
class

@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”选项:0上下文:nil]永远不会调用方法
observeValueForKeyPath:


使用
bind:
时是否需要调用
addObserver:
bind:
是否设置KVO?为什么绑定不起作用呢?

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

您需要的是一个针对images属性的实现的setter,如下所示。最常见的使用情况是,您需要使图形无效,并请求使用重新绘制
-设置需要显示:是

- (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:of对象:更改:上下文:
消息未发送的原因是,对于绑定,观察者不是绑定到对象的对象。背景中还有另一个物体。(在堆栈形式的断点中,您可以看到它的类是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”的属性更改。这可以通过覆盖集合访问器方法并在那里做更多的工作来完成,而不仅仅是接受更改。但是这有点复杂,因为有很多访问器方法(请参阅)。或者,更简单地说,您可以从视图中添加另一个观察关系

为此,在视图的初始化(
-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.
  }
}
请注意,这最后一个观察者关系与绑定不再有任何关系。绑定属性的值更改正确地到达视图,而您只是没有意识到(得到通知)


这应该是可行的。

没有机制,它只是为每个支持绑定的视图的每个绑定单独编码。这不是在自定义视图中可以免费获得的。如果不编写自定义实现,就不能使用exposeBinding:和bind:。有一些示例说明了如何做到这一点(但并不容易)这个答案不准确。绑定通过相同的机制工作,即使用KVO拾取更改,使用KVC将更改设置为绑定对象。绑定对象没有执行观察(KVO)但是,这就是为什么不调用
-observer-value:forKeyPath:change:
的原因。当您中断绑定setter方法时,您将发现涉及
NSEditableBinder
实例(但这似乎是未记录的API)。