使用组合扩展NSMutableDictionary时支持KVO

时间:2011-11-03 20:22:52

标签: objective-c cocoa nsdictionary key-value-observing

我有一个NSMutableDictionary对象数组,这些对象显示在主 - 详细信息界面中,该界面有一些文本字段和一堆复选框。控件绑定到字典键,通过数组控制器selection访问。

我想添加一些逻辑,当清除另一个复选框时清除一个复选框,如果在同一个会话中重新检查,则恢复原始值。由于我需要将存储与字典相关联,并且还需要添加代码,我以为我会使用合成来扩展NSMutableDictionary。

这就是我所做的:

  1. 我创建了一个LibraryEntry子类,其中包含一个NSMutableDictionary。
  2. 我实施了forwardInvocation:respondsToSelector:methodSignatureForSelector:,以及经过一次试错valueForUndefinedKey:
  3. 我创建了我的转发器对象。
  4. 我原样离开了绑定。
  5. 它可以很好地加载数据,但我猜测KVO无法正常工作。我猜测绑定器正在我的对象上调用addObserver:,但我没有实现任何特殊处理它。

    我想过简单地覆盖addObserver:并将消息转发到字典。但是,如果我这样做,observeValueForKey:通知将不会来自我的对象(addObserver的原始接收者),而是来自字典。

    在我尝试为这些KVO调用实现更透明的转发之前,我认为......这变得越来越混乱。我继续阅读“使用组合,而不是子类”来获得这样的行为。对于这种情况,这只是错误的模式吗?为什么?因为KVO?

    如果我放弃作曲并选择其中一种替代方案,似乎我会有更清晰的结果:

    1. 使用装饰器,一个实例观察每个字典
    2. 将临时密钥存储在字典中,让控制器在保存之前将其删除
    3. 取消字典并声明属性
    4. 这是我的代码,万一它有用(values是字典):

      - (void)forwardInvocation:(NSInvocation *)anInvocation {
          if ([values respondsToSelector:[anInvocation selector]])
              [anInvocation invokeWithTarget:values];
          else
              [super forwardInvocation:anInvocation];
      }
      
      - (BOOL)respondsToSelector:(SEL)aSelector {
          if ( [super respondsToSelector:aSelector] )
              return YES;
          else
              return [values respondsToSelector:aSelector];
      }
      
      - (NSMethodSignature*)methodSignatureForSelector:(SEL)selector {
          NSMethodSignature* signature = [super methodSignatureForSelector:selector];
          if (!signature) signature = [values methodSignatureForSelector:selector];
          return signature;
      }
      
      -(id)valueForUndefinedKey:(NSString *)key {
          return [values valueForKey:key];
      }
      

1 个答案:

答案 0 :(得分:1)

我认为结合使用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"]);


}

我通过创建一个跟踪块和userInfos的对象来实现块/ KVO连接,然后让它成为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

有一点不幸的是,你必须删除观察/行为才能打破原始字典和观察对象之间的传递保留周期,所以如果你不删除这些行为,你就是' ll泄漏集合。但总体而言,这种模式应该是有用的。

希望这有帮助。