Date
1 - 6 of 6
Binding to object returned by custom getter
Jonathan Taylor
Hi all,
I have a property "velocity" that returns a custom object representing a 2D vector. That vector object has properties "x" and "y". From time to time, the value of velocity to be displayed will change. Changes are deduced through keyPathsForValuesAffectingValueForKey logic (I'm just mentioning that in case it's relevant). I have a custom getter for velocity, which returns an autoreleased vector object that I construct then and there, containing the current velocity value. I then have a UI element that is bound to velocity.x and .y. When the velocity to be displayed changes, I get the following error: 2017-07-19 10:43:37.336 StackViewer[15059:303] Cannot update for observer <NSAutounbinderObservance 0x19f217590> for the key path "velocity.y" from <PIVSliceViewer 0x10194de80>, most likely because the value for the key "velocity" has changed without an appropriate KVO notification being sent. Check the KVO-compliance of the PIVSliceViewer class. I am a bit surprised at this - it had seemed to me that I was not doing anything unreasonable here, and I can't think of an obvious additional notification-related thing I should be adding to solve the problem. I am imagining it might have to do with the fact that I am returning a different (temporary) object for velocity, since I construct them on the fly in the getter - but that did not seem like an unreasonable thing to be doing. Does this symptom sound like something people would expect or recognise? Any suggestions about what I am missing that could solve this? I guess I might be able to fix things by introducing an explicit backing variable for the velocity property, and updating that, but I think I'd prefer my current approach (which I think is simpler and cleaner) if I can work out what the problem is here... Thanks for any suggestions Jonny |
|
Quincey Morris
On Jul 19, 2017, at 02:54 , Jonathan Taylor <jonathan.taylor@...> wrote:
Yes, that’s exactly the reason. By returning a new object from the getter, you are making the property non-KVO-compliant. KVO is reference-based, not value-based. When you observe the key-path “velocity.y” of some object, there is a actually a chain of observations: 1. some object (instance) observed for changes to key (property) “velocity” 2. some velocity (instance of the vector class) observed for changes to key “y” Returning a different velocity object from the first object returns an *unobserved* object, which causes the observation of the original vector instance to be meaningless. The potentially confusing part of this is that the *last* key in the key-path, although it represents an object**, isn’t observed. Observations are needed only when taking objects and properties pairwise. ** In your case, the property “y” might be a scalar, probably a CGFloat or a double. From the observer’s point of view, though, it is a NSNumber object. KVC has internal “magic” that automatically converts values of known scalar types (standard numeric and Boolean types, for example) into objects. |
|
Jack Brindle
I think I would have a property that holds the velocity that is calculated in response to the keyPathsForValues… method being fired.
toggle quoted message
Show quoted text
Then just bind to that property. Make sure that everything that is used to calculate velocity is included in the keyPathsForValues… method or this won’t work very well. It is permissible to use a method to do the calculating as it appears you have done, but you must call the willChange… and didChange… methods at the bingeing and end of the calculation in order for the KVO to succeed. You really cannot call the method just to get the value (it will fire the KVO, causing an infinite loop), which leads you right back to the method suggested above. - Jack On Jul 19, 2017, at 2:54 AM, Jonathan Taylor <jonathan.taylor@...> wrote: |
|
Alex Zavatone
KVO observes a known object. If you return another one, then you need to remove the observer from your old object and add it to your new one. KVO is going, “I’m looking at this thing.” If the accessor for that thing returns an identical copy of that thing, but one that is at another memory address, KVO doesn’t know that there’s a new thing. It’s still looking at the slot that holds the old thing. What’s even more dangerouserer is that if a KVO event was triggered on that old object and you check the KVO event data within observeValueForKeyPath: ofObject: change: context: and you check anything on the object without checking to see if it exists, you’re going to crash your app. While I love KVO, people much smarter than I prefer notifications sent to items in known scopes, rather than observing known objects for reasons like the crashy thing above. The known scoped object can choose to respond to the messages/notifications or to ignore them. I am welcome to be corrected here. - Z |
|
Quincey Morris
On Jul 19, 2017, at 13:14 , Jack Brindle <jackbrindle@...> wrote:
This represents a truth about KVO compliance for some scenarios, but the problem in this case is one level more subtle. The binding is observing the *wrong* instance of the velocity object. The only direct solution in this case is to use a stored property instead. (It can still be both derived and stored.) Other solutions: — Derive two additional properties, say “velocityX” and “velocityY”, and bind to those. — Write two value transformers, bind to “velocity”, and use the value transformers to get the X and Y components. But storing a reference to the current velocity object seems simpler. On Jul 19, 2017, at 13:31 , Alex Zavatone <zav@...> wrote:
You are correct, although I wouldn’t swear to the exact memory management consequences of this scenario. |
|
Jonathan Taylor
(Belated) thankyou to the various very helpful replies I had to this question of mine. I think I understand it now, and can see what is not going to work with my setup.
I still find the behaviour a little counter-intuitive in terms of working with a key *path* on an object (as opposed to a direct key). From a naive design point of view it would not seem totally unreasonable to me that I could observe the key path “velocity.y" and have the KVO system recognise that the object represented by the “velocity” property has changed (it should know, via keyPathsAffecting…), and therefore infer that the key path “velocity.y” needs re-examining. Or, to put it another way, I would very much understand that if I was binding to key path “y” on object “velocity” then it would be catastrophic if the underlying object “velocity” changed unexpectedly, but I would have imagined that binding to key path “velocity.y” on my model object would be exactly the way to avoid that problem. Oh well, evidently that’s not the case, so no point in me fretting about it! Thanks again for everyone’s help. Jonny. |
|