Я думаю, что с помощью сочетания хранилища, связанного с Objective-C, и некоторых блоков, вы можете подключить произвольное поведение к словарю (или любому другому объекту, совместимому с KVO) и таким образом решить вашу проблему. Я разработал следующую идею, которая реализует общий механизм KVO-triggers-block, и кодировал ее, а также пример, который, кажется, делает то, что вы хотите, и не включает в себя создание подклассов или декорирование базовых коллекций.
Первый публичный интерфейс этого механизма:
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
Это позволит вам прикреплять наблюдения / поведение на основе блоков к произвольным объектам. Задача, которую вы описываете с помощью флажков, может выглядеть примерно так:
- (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's previous value
NSLog(@"slaveCheckbox value: %@", [myModelDictionary objectForKey: @"slaveCheckbox"]);
}
Я реализовал подключение блоков / KVO, создав объект для отслеживания блоков и userInfos, а затем стал наблюдателем KVO. Вот что я сделал:
#import <objc/runtime.h>
static void* kKVOBehaviorsKey = &kKVOBehaviorsKey;
@interface KBKVOBehaviorObserver : NSObject
{
NSMutableDictionary* mBehaviorsByKey;
NSMutableDictionary* mUserInfosByKey;
}
@end
@implementation KBKVOBehaviorObserver
- (id)init
{
if (self = [super init])
{
mBehaviorsByKey = [[NSMutableDictionary alloc] init];
mUserInfosByKey = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void)dealloc
{
[mBehaviorsByKey release];
mBehaviorsByKey = nil;
[mUserInfosByKey release];
mUserInfosByKey = nil;
[super dealloc];
}
- (void)addBehavior: (KBBehavior)block forKeyPath: (NSString*)keyPath userInfo: (id)userInfo
{
@synchronized(self)
{
id copiedBlock = [[block copy] autorelease];
[mBehaviorsByKey setObject: copiedBlock forKey: keyPath];
[mUserInfosByKey setObject: userInfo forKey: keyPath];
}
}
- (void)removeBehaviorForKeyPath: (NSString*)keyPath
{
@synchronized(self)
{
[mUserInfosByKey removeObjectForKey: keyPath];
[mBehaviorsByKey removeObjectForKey: keyPath];
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == kKVOBehaviorsKey)
{
KBBehavior behavior = nil;
id userInfo = nil;
@synchronized(self)
{
behavior = [[[mBehaviorsByKey objectForKey: keyPath] retain] autorelease];
userInfo = [[[mUserInfosByKey objectForKey: keyPath] retain] autorelease];
}
if (behavior)
{
id oldValue = [change objectForKey: NSKeyValueChangeOldKey];
id newValue = [change objectForKey: NSKeyValueChangeNewKey];
behavior(object, keyPath, oldValue, newValue, userInfo);
}
}
}
@end
@implementation NSObject (KBKVOBehaviorObserver)
- (void)addBehavior: (KBBehavior)block forKeyPath: (NSString*)keyPath options: (NSKeyValueObservingOptions)options userInfo: (id)userInfo
{
KBKVOBehaviorObserver* obs = nil;
@synchronized(self)
{
obs = objc_getAssociatedObject(self, kKVOBehaviorsKey);
if (nil == obs)
{
obs = [[[KBKVOBehaviorObserver alloc] init] autorelease];
objc_setAssociatedObject(self, kKVOBehaviorsKey, obs, OBJC_ASSOCIATION_RETAIN);
}
}
// Put the behavior and userInfos into stuff...
[obs addBehavior: block forKeyPath: keyPath userInfo: userInfo];
// add the observation
[self addObserver: obs forKeyPath: keyPath options: options context: kKVOBehaviorsKey];
}
- (void)removeBehaviorForKeyPath: (NSString*)keyPath
{
KBKVOBehaviorObserver* obs = nil;
obs = [[objc_getAssociatedObject(self, kKVOBehaviorsKey) retain] autorelease];
// Remove the observation
[self removeObserver: obs forKeyPath: keyPath context: kKVOBehaviorsKey];
// remove the behavior
[obs removeBehaviorForKeyPath: keyPath];
}
@end
Одна вещь, которая вызывает сожаление, это то, что вам нужно удалить наблюдения / поведения, чтобы разорвать транзитивный цикл сохранения между исходным словарем и объектом наблюдения, поэтому, если вы не удалите поведения, вы ' пропущу коллекцию. Но в целом этот шаблон должен быть полезен.
Надеюсь, это поможет.