Re: rewriting observed keyPath
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
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
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
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
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
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
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