Cocoa 绑定和 KVO

发布于 2024-11-01 23:42:43 字数 1225 浏览 4 评论 0原文

我有一个视图 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!");
}

My 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:

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

I have a view MyView, and it has images which I want to bind with an array in my 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!");
}

My 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];
}

Without [self addObserver:view forKeyPath:@"images" options:0 context:nil]; the method observeValueForKeyPath: is never called.

Is it necessary to call addObserver: when using bind:? Does bind: set the KVO? And why doesn't binding work?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

动次打次papapa 2024-11-08 23:42:43

您需要的是图像属性的实现设置器,如下所示。最常见的用例是您需要使绘图无效并请求重绘
-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

呃。我只是意识到到目前为止我的答案假设了更简单的情况,即替换整个数组。您的问题是关于数组修改。 (不过,您确实在示例中声明了一个不可变的数组属性,它只允许替换。因此,请保持其声明状态,到目前为止我的方法将起作用。下面我将展示另一种选择。)

好的,让我们假设您在应用程序委托,替代品:

- (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 ];

这将在发送(绑定)端获取更改,然后通过绑定机制将其传输到视图,并最终

在视图的接收端 进行设置。 ,您需要将属性更改为@“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.
  }
}

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

那应该有效。

What you need is an implemented setter for the images property like below. The most common use-case for this is that you need to invalidate the drawing and request redraw with
-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
}

You can drop the -exposeBinding: call, since that has only influence on plugins for Interface Builder, and those where lost with the introduction of Xcode 4.

The reason why the -observeValueForKeyPath:ofObject:change:context: message is not send is that for a binding the observer is not the bound-to object. There is another object in the background. (In the stack form a breakpoint you can see that its class is NSEditableBinder.) So it is correct to register as observer from within the view to the view property @"images".

Another way to get notified about a change in the view is to override -setValue:forKey: method. Then you would need to check the key string and see if it was equal to @"images". But since there are other methods from the KVC protocol like -setValue:forKeyPath:, you would need to be extra careful to not disturb the machinery, i.e. always call super.

Uh. I just realize that my answer so far assumes the easier case where you replace the whole array. Your question was for an array modification. (You do declare an immutable array property in your example, though, which only allows replacement. So keep it as declared, and my approach so far will work. Below I show the other alternative.)

Ok, lets assume you do this in the app delegate, a replacement:

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

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

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

You don't need to post the change (using willChangeValueForKey: and didChangeValueForKey:, since you go through the declared property. They do that for you.

Now to the other approach where you modify an array. For that you need to use a mutable array property and modify it through an KVO-notifying proxy, like this:

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

This would pick up the change on the sending (bound-to) side. Then it would be transported to the view through the binding machinery, and eventually set using KVC.

There, on the receiving end in the view, you would need to pick up the property change to @"images". That could be done by overwriting the collection accessor method(s) and do more work there, instead of just accepting the the change. But that is a bit complicated, since there are quite a few accessor methods (See docs). Or, simpler, you could add another observation relationship from within the view.

For that, somewhere in initialization (-awakeFromNib: for example) of the view:

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

and then:

- (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.
  }
}

Note that this last observer relationship has nothing to do with the binding any longer. The value change to the bound property properly arrives at the view without, you just don't realize (get notified).

That should work.

心凉 2024-11-08 23:42:43

调用 observeValueForKeyPath 的唯一方法是调用 addObserver。绑定通过不同的机制进行。

The only way to have observeValueForKeyPath called is to call addObserver. Binding works through a different mechanism.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文