可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I'm trying to observe changes in dictionary using KVO.
Example:
dictionary = [NSMutableDictionary new]; [dictionary setObject:@"test1" forKey:@"key1"]; [dictionary setObject:@"test2" forKey:@"key2"]; [dictionary setObject:@"test3" forKey:@"key1"];
I'd love to be able to hook an observer for whenever a value is added to the dictionary. removed, or replaced (ie in the above cases, whenever any of the setObject methods are called)
So in conclusion: I want a function to have
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
called when I ADD a any new entry to a dictionary, or Remove any entry, or REPLACE any entry.
NOT: I do NOT want to have to specify which keys I'm observing for. (eg observe only when @"key1" is added) as this solution doesn't scale.
回答1:
Subclassing NSMutableDictionary is a bit annoying, due to the fact that NSDictionary and its friends are class clusters. It's certainly doable, and if you have to pass the object itself to another set of classes, then you may want to do exactly that. Otherwise, it might be easier to create a composite class which has the same basic API and uses NSMutableDictionary object internally for storage. There's a pretty good write-up as CocoaWithLove.com, Ordered Dictionary Subclassing, which goes into doing this.
However, that doesn't completely solve your problem. What I would suggest is that you begin with a subclass or decorator class such as the one above, then add support explicitly for -(NSArray*)allKeys
, which is a standard accessor in NSDictionary
itself. Then, you can add support to pass along change messages for allKeys, which will make it observable.
This can be done by adding the following code around the -setObject:forKey:
and -removeObjectForKey:
methods.
- (void)setObject:(id)anObject forKey:(id)aKey { BOOL addKey=NO; if (![dictionary objectForKey: aKey]) { addKey=YES; [self willChangeValueForKey: @"allKeys"]; } [dictionary setObject:anObject forKey:aKey]; if (addKey) [self didChangeValueForKey: @"allKeys"]; } - (void)removeObjectForKey:(id)aKey { [self willChangeValueForKey: @"allKeys"]; [dictionary removeObjectForKey:aKey]; [self didChangeValueForKey: @"allKeys"]; }
What is being done here is that we're adding explicit KVO notification to the class when the dictionary's keys are changed to mark a change in the array.
This will take care of adds and removes. If you want changes to be notified on the same basis, you can remove the if statements, and just have allKeys notify on either set or remove, like this:
- (void)setObject:(id)anObject forKey:(id)aKey { [self willChangeValueForKey: @"allKeys"]; [dictionary setObject:anObject forKey:aKey]; [self didChangeValueForKey: @"allKeys"]; }
Then, in your code, you put in a single observer for the key @"allKeys" on this object and you'll be receiving notifications whenever an item changes.
回答2:
I solved a similar problem by adding an observer to the mutable dictionary "translator" in this way:
[self addObserver:self forKeyPath:@"translator.@count" options:0 context:NULL];
My app manages the data in a the classical way, using a tableview, a controller and the dictionary as KVO property "translator". The dictionary is bound to a NSDictionaryController in my XIB, and a tableview content is bound to the controller.
This are the connections of the tableview:
Now, in any of the following cases I catch the change :
- adding a key-value pair
- removing a key-value pair
- changing a key
- changing a value
Remark: unfortunately, this approach does not work with NSMutableArrays. Changes are not recognized
回答3:
Can't you subclass NSMutableDictionary and override the various setters? For instance, overriding setObject:forKey: by calling super, then immediately calling addObserver...
You can also write a wrapper for NSMutableDictionary where you force yourself to use custom setters to manipulate the underlying NSMutableDictionary.
Maybe I need more context to any of your limitations or scalability intents.
回答4:
I hope this will be helpful
- (void)addObserver:(id)observer { for (id key in grid) [self addObserver:observer forKeyPath:[key description] options:0 context:key]; }
回答5:
I think another way to do this is using the below override, incase you are observing NSMutableDictionary "allRecentCurrencyData" whose values are dependent on recentBrazilReals, recentEuEuro, recentUkPounds, recentJapanYen, the observer will get called, but the drawback is you need to know the keys before hand to do this.
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key { NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key]; if ([key isEqualToString:@"allRecentCurrencyData"]) { NSArray *affectingKeys = @[@"recentBrazilReals", @"recentEuEuro",@"recentUkPounds",@"recentJapanYen"]; keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys]; } return keyPaths; }