Re: rewriting observed keyPath

2011-02-16 Thread Remco Poelstra

Op 15 feb 2011, om 23:59 heeft Quincey Morris het volgende geschreven:

 On Feb 15, 2011, at 11:27, Kyle Sluder wrote:
 
 As long as -current and -preset0 return the same object, and that
 object is KVO-compliant for @parameters, then observing
 @current.parameters and @preset0.parameters are equivalent.
 
 Kyle beat me to the punch on this part of the answer, but there's one 
 additional point -- the preferences controller *also* needs to be KVO 
 compliant with current, otherwise changing current will leave observers 
 of current.parameters out of date. The OP's originally posted attempt at an 
 implementation of the current property wasn't KVO compliant, but the 
 version I suggested was.
 

Thanks for the insights. It works fine now.
Leaves me wondering how that KVO registering works, how does the runtime now 
that it's the same path that is monitored? 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.

Regards,

Remco Poelstra

___

Cocoa-dev mailing list (Cocoa-dev@lists.apple.com)

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 arch...@mail-archive.com


Re: rewriting observed keyPath

2011-02-16 Thread Quincey Morris
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.



Re: rewriting observed keyPath

2011-02-16 Thread Remco Poelstra

Op 16 feb 2011, om 11:39 heeft Quincey Morris het volgende geschreven:

 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:

Thanks! That enlightens!

Regards,

Remco Poelstra

___

Cocoa-dev mailing list (Cocoa-dev@lists.apple.com)

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 arch...@mail-archive.com


Re: rewriting observed keyPath

2011-02-15 Thread Quincey Morris
On Feb 15, 2011, at 05:11, Remco Poelstra wrote:

 I've a PresetsController which holds a dictionary containing preset 
 settings for my application. The presets contain trees (Mutable Dictionaries) 
 of keys.
 To save the GUI code from bothering with tracking the current preset, I want 
 to give my PresetController the option to replace a keyPath like 
 @current.parameter.subparameter.value to 
 @preset2.parameter.subparameter.value.
 I implemented it with a valueForUndefinedKey:
 - (id) valueForUndefinedKey:(NSString *)key {
   if ([key isEqual:@current])
   return [presets valueForKey:currentPreset]; //presets is a 
 NSMutableDicitonary, currentPreset a NSString
   else
   return [presets valueForKey:key];
 }
 
 , returning the dictionary belonging to the current preset. This works for 
 setting and reading using keyPaths. It does not work for observing a keyPath 
 like @current.parameter.subparameter.value. How should I implement that?

I think you're making this too hard. If you need to observe a key path that 
includes the current key, then just define a [derived] current property for 
the presets controller (assuming that's the class that has the above code). The 
getter would look like this:

- (NSDictionary*) current {
return [presets valueForKey:currentPreset];
}

The only thing you have to do is make sure that current is KVO compliant, 
which means that notifications need to be sent out whenever the underlying 
value changes:

[self willChangeValueForKey: @current];
currentPreset = ...
[self didChangeValueForKey: @current];

wherever you change the current preset. There are other possible variations, 
depending on what's most convenient with your class:

1. Obviously you could vary this by keeping a pointer to the current preset 
dictionary in an instance variable too.

2. If currentPreset is itself a KVO compliant property, you don't need to 
generate KVO notifications manually (the second code fragment). Instead, you'd 
use:

+ (NSSet*) keyPathsForValuesAffectingCurrent {
return [NSSet setWithObject: @currentPreset];
}

and write:

self.currentPreset = ...


___

Cocoa-dev mailing list (Cocoa-dev@lists.apple.com)

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 arch...@mail-archive.com


Re: rewriting observed keyPath

2011-02-15 Thread Remco Poelstra

Hi,

Thanks for your reply.
The problem with this solution is that when I do a setValue:object  
forKeyPath:@preset0.parameter, and @preset0 is the current preset,  
than no KVO message is sent to observers observing the @current  
variant of the keyPath. That would only happen, if they used the  
actual @current.parameters keyPath.

Maybe KVO doesn't support this at all, I'm just hope it will :)

Regards,

Remco Poelstra

Op 15 feb 2011, om 19:51 heeft Quincey Morris het volgende geschreven:


On Feb 15, 2011, at 05:11, Remco Poelstra wrote:

I've a PresetsController which holds a dictionary containing  
preset settings for my application. The presets contain trees  
(Mutable Dictionaries) of keys.
To save the GUI code from bothering with tracking the current  
preset, I want to give my PresetController the option to replace a  
keyPath like @current.parameter.subparameter.value to  
@preset2.parameter.subparameter.value.

I implemented it with a valueForUndefinedKey:
- (id) valueForUndefinedKey:(NSString *)key {
if ([key isEqual:@current])
		return [presets valueForKey:currentPreset]; //presets is a  
NSMutableDicitonary, currentPreset a NSString

else
return [presets valueForKey:key];
}

, returning the dictionary belonging to the current preset. This  
works for setting and reading using keyPaths. It does not work for  
observing a keyPath like @current.parameter.subparameter.value.  
How should I implement that?


I think you're making this too hard. If you need to observe a key  
path that includes the current key, then just define a [derived]  
current property for the presets controller (assuming that's the  
class that has the above code). The getter would look like this:


- (NSDictionary*) current {
return [presets valueForKey:currentPreset];
}

The only thing you have to do is make sure that current is KVO  
compliant, which means that notifications need to be sent out  
whenever the underlying value changes:


[self willChangeValueForKey: @current];
currentPreset = ...
[self didChangeValueForKey: @current];

wherever you change the current preset. There are other possible  
variations, depending on what's most convenient with your class:


1. Obviously you could vary this by keeping a pointer to the current  
preset dictionary in an instance variable too.


2. If currentPreset is itself a KVO compliant property, you don't  
need to generate KVO notifications manually (the second code  
fragment). Instead, you'd use:


+ (NSSet*) keyPathsForValuesAffectingCurrent {
return [NSSet setWithObject: @currentPreset];
}

and write:

self.currentPreset = ...




___

Cocoa-dev mailing list (Cocoa-dev@lists.apple.com)

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 arch...@mail-archive.com


Re: rewriting observed keyPath

2011-02-15 Thread Kyle Sluder
On Tue, Feb 15, 2011 at 11:09 AM, Remco Poelstra re...@beryllium.net wrote:
 Thanks for your reply.
 The problem with this solution is that when I do a setValue:object
 forKeyPath:@preset0.parameter, and @preset0 is the current preset, than
 no KVO message is sent to observers observing the @current variant of the
 keyPath. That would only happen, if they used the actual
 @current.parameters keyPath.
 Maybe KVO doesn't support this at all, I'm just hope it will :)

As long as -current and -preset0 return the same object, and that
object is KVO-compliant for @parameters, then observing
@current.parameters and @preset0.parameters are equivalent.

--Kyle Sluder
___

Cocoa-dev mailing list (Cocoa-dev@lists.apple.com)

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 arch...@mail-archive.com


Re: rewriting observed keyPath

2011-02-15 Thread Quincey Morris
On Feb 15, 2011, at 11:27, Kyle Sluder wrote:

 As long as -current and -preset0 return the same object, and that
 object is KVO-compliant for @parameters, then observing
 @current.parameters and @preset0.parameters are equivalent.

Kyle beat me to the punch on this part of the answer, but there's one 
additional point -- the preferences controller *also* needs to be KVO compliant 
with current, otherwise changing current will leave observers of 
current.parameters out of date. The OP's originally posted attempt at an 
implementation of the current property wasn't KVO compliant, but the version 
I suggested was.

___

Cocoa-dev mailing list (Cocoa-dev@lists.apple.com)

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 arch...@mail-archive.com