Re: rewriting observed keyPath
Re: rewriting observed keyPath
- Subject: Re: rewriting observed keyPath
- From: Quincey Morris <email@hidden>
- Date: Wed, 16 Feb 2011 02:39:59 -0800
On Feb 16, 2011, at 00:19, Remco Poelstra wrote:
> Leaves me wondering how that KVO registering works, how does the runtime now that it's the same path that is monitored?
Actually, there's no magic involved* -- it's kind of obvious**, in the sense that it has to work this way***, even if the process is a bit complicated. If you're interested, here are the gory details****:
Let's say you tell object A to observe object B on key path "x.y.z". That causes the following observations to be set up:
#1. A internally observes B on key "x"
#2. A internally observes the current value of object B.x (call it X1) on key "y"
#3. A internally observes the current value of object X1.y (call it Y1) on key "z"
with the proviso that this chain of observations ends early if any of the objects is nil. (For example, if X1 is nil, A just observes B on "x" and that's all.)
Let's suppose the value of Y1.z changes (KVO compliantly, of course). Object A gets notified internally that Y1 changed for property "z", which it then reports to your 'observeValueForKeyPath:ofObject:change:context:' as a change to B on its "x.y.z" key path.
(If you didn't set up the observation explicitly, but are using bindings, then "you" means the binding, because the binding set up the observation.)
There's an important fact to notice here, that relates to your original question. Your 'observeValueForKeyPath:ofObject:change:context:' will report the change to Y1.z *no matter how that change came about*. Specifically, if B.current is also X1, and B.x.y.z got changed, you'll get told that B changed for key path "current.y.z" if that's the observation you registered for, or that B changed for key path "x.y.z" if that's the observation you registered for. (Notice that this isn't where your original design failed. For that, you'll have to read on.)
--
Let's suppose the value of B.x changes, from X1 to X2. Object A gets notified internally. It removes the observation of X1 on key path "y.z" (which removes #2 and #3), and adds an observation of X2 on key path "y.z" (which adds a new #2 and #3). As in the previous case, it then reports a change to B's "x.y.z" key path to your 'observeValueForKeyPath:ofObject:change:context:'.
If you work through the details, you'll see that this process works fine even if some of B.x, B.x.y or B.x.y.z are nil, or change to or from nil -- the only difference is that there are fewer of the internal observations. (B can't be nil, though, otherwise you'd have sent 'addObserver:...' to a nil receiver, and so no observation would ever have been registered. This is sometimes an annoyance, because B might be nil during A's initialization, which is where you'd like to add observations. One way around this is to have A observe its own "b" property, and use *that* notification to add or remove the rest of the observations. Or, sometimes, B will be known to exist at A's 'awakeFromNib' time, which is why observations are often set up there rather than in initialization.)
Back to your original problem one more time. If B.x changes, but you actually observed B on key path "current.y.z", what happens? Well, apparently nothing -- the triggering KVO notification is for property "x", but you aren't registered for a sequence of internal observations starting from key "x", so you miss out on this change, and any subsequent changes to B.x.y or B.x.y.z. That's why your original design failed.
So how come it works now? Consider what happens when property "current" is dependent on property "x" and is KVO compliant. Then, if B.x changes, a *second* KVO notification is produced for property "current", and *that* causes your observation of key path "current.y.z" to be triggered. It works, not because there's anything different about the observations, but because KVO essentially *transfers* the change notification from one key path to another.
General rule: If you get the KVO compliance right, everything will work.
--
Your statement:
> Especially in the situation where the full keyPath might not yet exist. You can still register for it and be notified as soon as it's created.
is only *very* approximate. The key path (which is merely a string, like "x.y.z") *always* exists -- it's the chain of object values that the key path represents that might not fully exist due to nil values. You're not notified as soon as anything is "created", but as soon as anything in the chain of object values changes. In the so-called "created" case, you're just seeing an observed value change from nil to non-nil.
--
* TBH, I'm not sure there isn't any magic.
** Obviously, I'm using the word "obvious" in a non-obvious sense.
*** My description of how it works is conceptual -- the actual implementation is unknown.
**** I've done my best to be precisely accurate, but it's easy to make mistakes when talking about KVC and KVO. It's possible that anything or everything in my description is utterly wrong.
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden