observeValueForKeyPath:ofObject:change:context: 无法与数组正常工作
我有一个对象,它实现了名为 contents
的键的索引访问器方法。 在这些访问器中,当我修改底层数组时,我会调用 willChange:valuesAtIndexes:forKey:
和 didChange:valuesAtIndexes:forKey:
。
我还有一个自定义视图对象,它通过 NSArrayController
绑定到 contents
。 在 observeValueForKeyPath:ofObject:change:context:
中,我见过的 NSKeyValueChangeKindKey
更改字典中的唯一值是 NSKeyValueChangeSetting
。 当我向数组添加对象时,我希望看到 NSKeyValueChangeInsertion
。
每次插入单个项目时,重新创建视图所观察到的对象的内部表示(尤其是当我批量加载数百个项目时),正如您所想象的那样,会带来相当大的性能问题。 我做错了什么,Cocoa 似乎认为我每次添加或删除单个项目时都设置一个全新的数组?
I have an object that implements the indexed accessor methods for a key called contents
. In those accessors, I call willChange:valuesAtIndexes:forKey:
and didChange:valuesAtIndexes:forKey:
when I modify the underlying array.
I also have a custom view object that is bound to contents
via an NSArrayController
. In observeValueForKeyPath:ofObject:change:context:
the only value in the change dictionary for the NSKeyValueChangeKindKey
I ever see is NSKeyValueChangeSetting
. When I'm adding objects to the array, I expect to see NSKeyValueChangeInsertion
.
Recreating my view's internal representation of the objects it observes every time I insert a single item -- particularly when I'm bulk loading hundreds of items -- presents quite a performance problem, as you'd imagine. What am I doing wrong that Cocoa seems to think I'm setting a completely new array each time I add or remove a single item?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
(所有读者请注意:我也讨厌使用答案,但是这个讨论太长了,无法发表评论。当然,缺点是它最终没有按时间顺序排序。如果你不喜欢它,我建议你向 Stack Overflow 管理员抱怨评论长度有限且仅限纯文本。)
为作为绑定公开的可变数组属性实现访问器,包括索引访问器。
还有KVC。
当然,覆盖这是一种方法。 另一种方法是在具有可绑定属性(视图)的对象中实现访问器。
Cocoa Bindings 将为您调用视图的访问器(大概使用 KVC)。 您不需要实现 KVO 观察方法(当然,除非您直接使用 KVO)。
我知道这一点,因为我就是这样做的。 请参阅 CPU 使用情况中的 PRHGradientView。
奇怪的是,文档中没有提到这一点。 我将提交一个有关它的文档错误 - 要么我正在做一些脆弱的事情,要么他们忘记在文档中提及这个非常好的功能。
有相当多的人从事所谓“过早优化”的工作。 如果不问,我无法知道谁是其中之一。
(Note to all readers: I hate using answers for this, too, but this discussion is too long for comments. The downside, of course, is that it ends up not sorted chronologically. If you don't like it, I suggest you complain to the Stack Overflow admins about comments being length-limited and plain-text-only.)
Implement accessors, including indexed accessors, for the mutable array property that you've exposed as a binding.
And KVC.
Overriding that is one way, sure. The other way is to implement accessors in the object with the bindable property (the view).
Cocoa Bindings will call your view's accessors for you (presumably using KVC). You don't need to implement the KVO observe method (unless, of course, you're using KVO directly).
I know this because I've done it that way. See PRHGradientView in CPU Usage.
Curiously, the documentation doesn't mention this. I'm going to file a documentation bug about it—either I'm doing something fragile or they forgot to mention this very nice feature in the docs.
There are quite a large number of people who engage in something called “premature optimization”. I have no way of knowing who is one of them without asking.
不要那样做。 当您收到发给其中一位访问者的消息时,KVO 会为您发布通知。
一方面,为什么直接使用 KVO? 使用
bind:toObject:withKeyPath:options:
将视图的属性绑定到数组控制器的arrangedObjects
(我假设)属性,并实现数组访问器(包括索引访问器,如果您就像)在视图中。另一方面,请记住
arrangedObjects
是派生属性。 数组控制器将对其内容数组进行过滤和排序; 结果是arrangedObjects
。 您可能会争辩说,将原始插入中的索引排列为新插入将是将第一个更改更准确地转换为第二个更改,但设置整个arrangedObjects
数组可能更容易实现(类似于[self _setArrangedObjects:[[newArrayfilteredArrayUsingPredicate:self.filterPredicate]sortedArrayUsingDescriptors:self.sortDescriptors]]
)。真的有关系吗? 您是否进行过分析并发现您的应用程序在大规模更换阵列时速度很慢?
如果是这样,您可能需要将视图直接绑定到数组的
content
属性或底层对象上的原始数组,并遭受自由过滤和排序的损失。Don't do that. KVO posts the notifications for you when you receive a message to one of those accessors.
For one thing, why are you using KVO directly? Use
bind:toObject:withKeyPath:options:
to bind the view's property to the array controller'sarrangedObjects
(I assume) property, and implement array accessors (including indexed accessors, if you like) in the view.For another, remember that
arrangedObjects
is a derived property. The array controller will filter and sort its content array; the result isarrangedObjects
. You could argue that permuting the indexes from the original insertion into a new insertion would be a more accurate translation of the first change into the second, but setting the entirearrangedObjects
array was probably simpler to implement (something like[self _setArrangedObjects:[[newArray filteredArrayUsingPredicate:self.filterPredicate] sortedArrayUsingDescriptors:self.sortDescriptors]]
).Does it really matter? Have you profiled and found that your app is slow with wholesale array replacement?
If so, you may need to bind the view directly to the array's
content
property or to the original array on the underlying object, and suffer the loss of free filtering and sorting.由于超出本问题范围的原因,我手动调用 KVO 方法。 我已禁用此属性的自动观察。 我知道我在那里做什么。
我不明白在视图中实现数组访问器是什么意思。 Bindings 构建在 KVO 之上。 所有绑定都是使用 observeValueForKeyPath: 实现的。我的自定义视图提供了应用程序绑定到数组(或者在本例中为数组控制器)的绑定。 访问器方法适用于 KVC,不适用于 KVO。
我在每次阵列更新时都会收到一条设置消息,这绝对很重要。 如果不重要的话,我不会将其作为问题发布。 我称
每次插入时,我的视图都会观察到一个全新的数组。 由于我可能会添加数百个对象,因此本应为
O(N)
的时间却是O(N^2)
。 抛开性能问题不谈,这是完全不可接受的。 但是,既然你提到了这一点,视图确实需要做大量的工作来观察一个全新的数组,这会显着减慢程序的速度。我不能放弃使用数组控制器,因为我需要过滤和排序,并且因为有一个
NSTableView
绑定到同一个控制器。 我依靠它来保持排序和选择同步。我通过彻底的 hack 解决了我的问题。 我编写了一个单独的方法来手动调用 KVO 方法,以便只发送一条 KVO 消息。 这是一个黑客,我不喜欢它,它仍然使我的视图重新读取整个数组 - 虽然现在只有一次 - 但它现在有效,直到我找到更好的解决方案。
I call the KVO methods manually for reasons outside the scope of this issue. I have disabled automatic observing for this property. I know what I'm doing there.
I don't understand what you mean by implementing array accessors in the view. Bindings is built on top of KVO. All bindings are implemented using
observeValueForKeyPath:
My custom view provides a binding that the app binds to an array -- or in this case, an array controller. Accessor methods apply to KVC, not KVO.It absolutely matters that I'm getting a set message on every array update. I wouldn't have posted it as a question if it didn't matter. I call something like
On every insertion, my view observes a whole new array. Since I'm potentially adding hundreds of objects, that's
O(N^2)
when it should to beO(N)
. That is completely unacceptable, performance issues aside. But, since you mention it, the view does have to do a fair amount of work to observe a whole new array, which significantly slows down the program.I can't give up using an array controller because I need the filtering and sorting, and because there's an
NSTableView
bound to the same controller. I rely on it to keep the sorting and selections in sync.I solved my problem with a complete hack. I wrote a separate method that calls the KVO methods manually so that only one KVO message is sent. It's a hack, I don't like it, and it still makes my view reread the entire array -- although only once, now -- but it works for now until I figure out a better solution.