I think that with a combination of Objective-C linked storage and some blocks, you can connect arbitrary behavior to a dictionary (or any other object compatible with KVO) and solve this problem this way. I have prepared the following idea, which implements the general mechanism of KVO-trigger-blocks and an encoded example and an example that seems to fulfill what you want and does not require subclassing or decorating the foundation collections.
First, the open interface of this mechanism:
typedef void (^KBBehavior)(id object, NSString* keyPath, id oldValue, id newValue, id userInfo); @interface NSObject (KBKVOBehaviorObserver) - (void)addBehavior: (KBBehavior)block forKeyPath: (NSString*)keyPath options: (NSKeyValueObservingOptions)options userInfo: (id)userInfo; - (void)removeBehaviorForKeyPath: (NSString*)keyPath; @end
This will allow you to bind block-based observations / behaviors to arbitrary objects. The task that you describe using checkboxes might look something like this:
- (void)testBehaviors { NSMutableDictionary* myModelDictionary = [NSMutableDictionary dictionary]; KBBehavior behaviorBlock = ^(id object, NSString* keyPath, id oldValue, id newValue, id userInfo) { NSMutableDictionary* modelDictionary = (NSMutableDictionary*)object; NSMutableDictionary* previousValues = (NSMutableDictionary*)userInfo; if (nil == newValue || (![newValue boolValue])) { // If the master is turning off, turn off the slave, but make a note of the previous value id previousValue = [modelDictionary objectForKey: @"slaveCheckbox"]; if (previousValue) [previousValues setObject: previousValue forKey: @"slaveCheckbox"]; else [previousValues removeObjectForKey: @"slaveCheckbox"]; [modelDictionary setObject: newValue forKey: @"slaveCheckbox"]; } else { // if the master is turning ON, restore the previous value of the slave id prevValue = [previousValues objectForKey: @"slaveCheckbox"]; if (prevValue) [modelDictionary setObject:prevValue forKey: @"slaveCheckbox"]; else [modelDictionary removeObjectForKey: @"slaveCheckbox"]; } }; // Set the state... [myModelDictionary setObject: [NSNumber numberWithBool: YES] forKey: @"slaveCheckbox"]; [myModelDictionary setObject: [NSNumber numberWithBool: YES] forKey: @"masterCheckbox"]; // Add behavior [myModelDictionary addBehavior: behaviorBlock forKeyPath: @"masterCheckbox" options: NSKeyValueObservingOptionNew userInfo: [NSMutableDictionary dictionary]]; // turn off the master [myModelDictionary setObject: [NSNumber numberWithBool: NO] forKey: @"masterCheckbox"]; // we now expect the slave to be off... NSLog(@"slaveCheckbox value: %@", [myModelDictionary objectForKey: @"slaveCheckbox"]); // turn the master back on... [myModelDictionary setObject: [NSNumber numberWithBool: YES] forKey: @"masterCheckbox"]; // now we expect the slave to be back on, since that was it previous value NSLog(@"slaveCheckbox value: %@", [myModelDictionary objectForKey: @"slaveCheckbox"]); }
I implemented block / KVO binding by creating an object to track blocks and userInfos, and then it will be a KVO observer. Here is what I did:
One thing that is regrettable is that you need to remove cases / behavior in order to break the transitive save cycle between the original dictionary and the object of observation, so if you do not delete the behavior, the collection leaks. But overall, this template should be useful.
Hope this helps.