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 protected])

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:
http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to [email protected]

Reply via email to