• Open Menu Close Menu
  • Apple
  • Shopping Bag
  • Apple
  • Mac
  • iPad
  • iPhone
  • Watch
  • TV
  • Music
  • Support
  • Search apple.com
  • Shopping Bag

Lists

Open Menu Close Menu
  • Terms and Conditions
  • Lists hosted on this site
  • Email the Postmaster
  • Tips for posting to public mailing lists
Re: Bindings - registering change notification for multiple keys
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Bindings - registering change notification for multiple keys


  • Subject: Re: Bindings - registering change notification for multiple keys
  • From: Ken Thomases <email@hidden>
  • Date: Wed, 2 Jul 2008 03:16:12 -0500

On Jul 1, 2008, at 1:38 PM, dreamcat7 wrote:

I would like to to save the changed items permanently in my data store after the user interacts with the ui. Control elements are bound to a settings NSDictionary in my data model.


For any bindings notifications, the following method

- (void)registerAsObserver
{
[model addObserver:self forKeyPath:@"prefs.debug"
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
context:NULL];
}


will only register for observing one value at a time. However I may have a wide variety of controls and settings that need to trigger the same code to write out the dictionary to the plist file (or to any arbitrary data storage mechanism i might use). And cant write anything like @"prefs.*" because this syntax does not constitute a valid key-path.
And if there is any way to listen for all values within a path or set.

This is one of the pitfalls of using an NS(Mutable)Dictionary as a model object, rather than as merely an implementation detail of one of your model objects.


Given the code snippet you provided, your model contains at least two objects: the one you refer to as "model" and a dictionary. "model" has a property named "prefs" which is a to-one relationship to the dictionary. So, your "model" does not have a property regarding the "debug" value, it only has a relationship to an object with such a property.

As a model object, NSMutableDictionary is somewhat problematic. It is only KVO-compliant if it is modified with setValue:forKey:. Any change made via setObject:forKey:, the NSMutableDictionary primitive method, is not broadcast to observers.

More significantly, one of the central tenets of OOP is encapsulation. You, the model class programmer, need to always be in charge of access to your objects' data. If somebody wishes to modify that data, they need to invoke your custom code as the gatekeeper. With a custom model class, this is straightforward. However, if you expose an NSMutableDictionary as part of your model, rather than keeping it a private encapsulated part of the model's implementation, then client code will only have to ask your model for that mutable dictionary and after that can bypass your custom code. This is true even if they use setValue:forKeyPath: to perform the modification. In that case, your custom code will have its -prefs method invoked, but none of your methods will be invoked when the dictionary is actually modified.

Put another way, your custom code is able to act as a gatekeeper for gaining access to the mutable dictionary, but once it hands out that access there is no longer any opportunity for your custom code to exercise control over, or respond to, what a client does with that mutable dictionary.

Here's what I recommend: either 1) demote the NSMutableDictionary which is currently the "prefs" property of "model" to an implementation detail, and make its keys into properties of "model" itself, or 2) promote the model state which is currently maintained in the NSMutableDictionary to a proper model object by wrapping it in a custom class (say, "Preferences") -- again, making the dictionary an implementation detail -- and making the keys into properties of the Preferences class. In either case, there should be not key path which directly accesses the NSMutableDictionary without going through a custom setter of your own making.

Now, how to deal with all those properties? Well, do you really need for the set of properties to be dynamic, or is it in fact static and you're just annoyed by the size (and the attendant repetitive work)?

If you really need for there to be a dynamic set of properties, you can accomplish that using valueForUndefinedKey: and setValue:forUndefinedKey:. Basically, you have "virtual" properties -- they won't have proper individual accessors, but the above-named methods will be invoked whenever anything tries to access them via KVC, and in your implementations you can simulate their existence (by accessing the NSMutableDictionary which is part of your implementation, for example).

Whether the set of properties is static or dynamic, it's troublesome to observe them all. One possible solution for that is to use a synthetic property which represents the state of the whole set of properties. This property exists solely so that clients of the class may watch for changes in this multi-property state. It has no meaningful value. That is, clients may observe it, but they'll never really care what its value is, only when it "changes".

Let's suppose you go the route of creating a Preferences class. In that case, let's call the synthetic property "anyPreferences". It's read-only, so we just need a simple getter:

-(id)anyPreferences
{
return self;
// The choice to return self is essentially arbitrary
// We could instead return a different NSObject each time: return [[[NSObject alloc] init] autorelease];
// That would guarantee that any attempt to compare the "old" and "new" values of this property would indicate a difference,
// but it could cause a spike in memory usage.
}


Now, classes can observe an instance of Preferences for changes in its "anyPreferences" property. But under what circumstance would they be notified of a change in that property? Well, the normal ones. Something should arrange for will/didChangeValueForKey: to be called on the instance of Preferences with @"anyPreferences" as the parameter.

In the case of a static set of properties, the best thing to do is implement +keyPathsForValuesAffectingAnyPreferences in the Preferences class and return a set of the names of all of those properties:

+keyPathsForValuesAffectingAnyPreferences
{
return [NSSet setWithObjects:@"debug", /* list the other preference names here... */, nil];
}


(If you're targeting Tiger, you would instead use +setKeys:triggerChangeNotificationsForDependentKey: to accomplish this same thing:

+(void) initialize
{
if (self == [Preferences class])
{
[self setKeys:[NSArray arrayWithObjects:@"debug", /* list the other preference names here... */, nil]
triggerChangeNotificationsForDependentKey:@"anyPreferences"];
}
}


)

By doing this, you arrange for the KVO machinery itself to generate the necessary will/didChangeValueForKey:@anyPreferences" calls whenever you modify one of the other properties in a KVO-compliant manner.

The above approach won't work for a dynamic set of properties. Notice that +keyPathsForValuesAffectingAnyPreferences is a class method (as is +initialize, where +setKeys:triggerChangeNotificationsForDependentKey: is called). It must do its work without reference to any specific instance of the Preferences class, so it can't learn what the dynamic set of properties supported by such an instance is. In that case, make your implementation of setValue:forUndefinedKey: manually trigger notifications of change in the "anyPreferences" property, by explicitly invoking will/didChangeValueForKey: before and after performing the actual modification.


So i am looking at other ways. Can you listen for all objects held by an NSArray ?

Sort of. You can observe a to-many property (which is often implemented as an NS(Mutable)Array) of an object. Keep in mind, though, to only modify the to-many relationship in KVO-compliant fashion.



Of course there is the method setKeys: triggerChangeNotificationsForDependentKey: however i did find all the explanations about this really difficult to follow.

Basically, it informs the KVO machinery of a relationship among the properties of the class upon which it's called. It says that a certain property, named by the dependentKey parameter, should be considered to have changed whenever any of the other named properties changes. Where I used it above, I was telling KVO that the "anyPreferences" property should be considered to have changed whenever the "debug" property changes. This causes KVO to send out its change notifications for "anyPreferences" even though nothing specifically modified that property (i.e. nobody ever is likely to invoke [somePreferencesInstance setValue:@"blah" forKey:@"anyPreferences"]).


Awhile back i tried doing with the apple example but unfortunately no luck.

Dunno what problems you had, so can't clear them up. :)

And don't the array of triggering keys need to be first registered individually anyway ? (perhaps thats why it failed to work for me).

No. Properties aren't "registered", really. KVC/KVO have various documented ways of trying to access a property when required. They do this on demand at run-time. There isn't a list anywhere of the properties for a class. If an attempt to access the property doesn't find accessor methods or instance variables, they fall back onto the "undefined key" methods I mentioned earlier. The default implementations of those methods raise an exception, which is the only indication that a property doesn't exist. If you override those methods to do something else, then you can extend the set of properties in any fashion you care to imagine -- again, dynamically at run-time.



I know this is a lot to take in. I'm verbose like that. Sorry. ;)

I hope it's helpful, though.

Cheers,
Ken
_______________________________________________

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


  • Follow-Ups:
    • Re: Bindings - registering change notification for multiple keys
      • From: Ken Thomases <email@hidden>
    • Re: Bindings - registering change notification for multiple keys
      • From: dreamcat7 <email@hidden>
References: 
 >Bindings - registering change notification for multiple keys (From: dreamcat7 <email@hidden>)

  • Prev by Date: Re: Process app.
  • Next by Date: Re: Process app.
  • Previous by thread: Bindings - registering change notification for multiple keys
  • Next by thread: Re: Bindings - registering change notification for multiple keys
  • Index(es):
    • Date
    • Thread