【问题标题】:Cocoa-bindings and KVOCocoa 绑定和 KVO
【发布时间】:2011-04-16 21:12:27
【问题描述】:

我有一个视图 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:

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

【问题讨论】:

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


    【解决方案1】:

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

    这将在发送(绑定到)端获取更改。然后它将通过绑定机制传输到视图,并最终使用 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.
      }
    }
    

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

    应该可以的。

    【讨论】:

      【解决方案2】:

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

      【讨论】:

      • 没有机制,只是为每个支持Bindings的View的每个绑定单独编码。这不是您可以在自定义视图中免费获得的东西。你不能使用exposeBinding: 和bind: 而不编写他们的自定义实现。有如何做到这一点的例子(但这并不容易)
      • 这个答案不准确。绑定通过相同的机制工作,即它们使用 KVO 获取更改,并使用 KVC 将它们设置为绑定对象。但是,绑定对象本身并没有执行观察 (KVO) 部分,这就是为什么不调用 -observerValue:forKeyPath:change: 的原因。当您中断绑定的 setter 方法时,您会发现涉及一个 NSEditableBinder 实例(但这似乎是未记录的 API)。
      猜你喜欢
      • 1970-01-01
      • 2013-12-04
      • 2023-04-10
      • 1970-01-01
      • 2013-08-29
      • 2012-01-27
      • 2019-06-06
      • 1970-01-01
      • 2017-02-25
      相关资源
      最近更新 更多