Objective c 使用composition扩展NSMutableDictionary时支持KVO

Objective c 使用composition扩展NSMutableDictionary时支持KVO,objective-c,cocoa,nsdictionary,key-value-observing,Objective C,Cocoa,Nsdictionary,Key Value Observing,我有一个NSMutableDictionary对象数组,它们显示在主-详细界面中,该界面有几个文本字段和一组复选框。这些控件绑定到字典键,可通过阵列控制器的选择访问 我想添加一些逻辑,当一个复选框被清除时,它会清除另一个复选框,如果在同一个会话中重新检查,它会恢复原始值。因为我需要将存储和字典关联起来,并且还需要添加代码,所以我想我应该使用组合来扩展NSMutableDictionary 以下是我所做的: 我创建了一个LibraryEntry子类,其中包含一个NSMutableDictionar

我有一个NSMutableDictionary对象数组,它们显示在主-详细界面中,该界面有几个文本字段和一组复选框。这些控件绑定到字典键,可通过阵列控制器的
选择访问

我想添加一些逻辑,当一个复选框被清除时,它会清除另一个复选框,如果在同一个会话中重新检查,它会恢复原始值。因为我需要将存储和字典关联起来,并且还需要添加代码,所以我想我应该使用组合来扩展NSMutableDictionary

以下是我所做的:

  • 我创建了一个
    LibraryEntry
    子类,其中包含一个NSMutableDictionary
  • 我实现了
    forwardInvocation:
    响应选择器:
    方法签名选择器:
    ,并且在一些尝试和错误之后
    valueforundinedkey:
  • 我创建了我的转发器对象
  • 我把装束留在原处
  • 它可以很好地加载数据,但我猜KVO不会正常工作。我猜活页夹在my对象上调用了
    addObserver:
    ,但我还没有实现任何特殊的方法来处理它

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

    在我尝试为这些KVO呼叫实现更透明的转发之前,我想。。。这越来越乱了。我一直在读“使用组合,而不是子类化”来获得这样的行为。这只是这种情况下的错误模式吗?为什么?因为KVO

    如果我放弃构图,选择以下选项之一,我似乎会得到更清晰的结果:

  • 使用decorator,每个字典有一个实例
  • 将临时密钥存储在字典中,并在保存前让控制器删除它们
  • 不用字典,而是声明属性
  • 这是我的代码,以防有用(
    values
    是字典):


    我认为,结合Objective-C相关存储和一些块,您可以将任意行为连接到字典(或任何其他兼容KVO的对象),并以这种方式解决您的问题。我编写了下面的想法,它实现了一个通用的KVO触发器块机制,并编写了一个示例,它显示了您想要做的事情,并且不涉及子类或装饰基础集合。 首先是该机制的公共接口:

    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"]);
    
    
    }
    
    我通过创建一个对象来跟踪块和用户信息来实现block/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
    
    #导入
    静态void*kKVOBehaviorsKey=&kKVOBehaviorsKey;
    @接口KBKVOBehaviorObserver:NSObject
    {
    NSMutableDictionary*mBehaviorsByKey;
    NSMutableDictionary*mUserInfosByKey;
    }
    @结束
    @KBKVOBehaviorObserver的实现
    -(id)init
    {
    if(self=[super init])
    {
    mBehaviorsByKey=[[NSMutableDictionary alloc]init];
    mUserInfosByKey=[[NSMutableDictionary alloc]init];
    }
    回归自我;
    }
    -(无效)解除锁定
    {
    [mBehaviorsByKey发布];
    mBehaviorsByKey=零;
    [mUserInfosByKey发布];
    mUserInfosByKey=零;
    [super dealoc];
    }
    -(void)addBehavior:(KBBehavior)块forKeyPath:(NSString*)keyPath userInfo:(id)userInfo
    {
    @同步(自)
    {
    id copiedBlock=[[block copy]autorelease];
    [mBehaviorsByKey setObject:copiedBlock-forKey:keyPath];
    [mUserInfosByKey setObject:userInfo-forKey:keyPath];
    }
    }
    -(void)removeBehaviorForKeyPath:(NSString*)键路径
    {
    @同步(自)
    {
    [mUserInfosByKey removeObjectForKey:keyPath];
    [mBehaviorsByKey removeObjectForKey:keyPath];
    }
    }
    -(void)observeValueForKeyPath:(NSString*)对象的键路径:(id)对象更改:(NSDictionary*)更改上下文:(void*)上下文
    {
    if(context==kKVOBehaviorsKey)
    {
    KBBehavior=nil;
    id userInfo=nil;
    @同步(自)
    {
    行为=[[mBehaviorsByKey对象forkey:keyPath]retain]autorelease];
    userInfo=[[mUserInfosByKey objectForKey:keyPath]retain]autorelease];
    }
    如果(行为)
    {
    id oldValue=[change objectForKey:NSKeyValueChangeOldKey];
    id newValue=[change objectForKey:NSKeyValueChangeNewKey];
    行为(对象、键路径、旧值、新值、用户信息);
    }
    }
    }
    @结束
    @实现NSObject(KBKVOBehaviorObserver)
    -(void)addBehavior:(KBBehavior)块forKeyPath:(NSString*)键路径选项:(NSKeyValueObservingOptions)选项userInfo:(id)userInfo
    {
    KBKVOBehaviorObserver*obs=nil;
    @同步(自)
    {
    obs=objc_getassociated对象(self,kKVOBehaviorsKey);
    if(nil==obs)
    {
    obs=[[KBKVOBehaviorObserver alloc]init]autorelease];
    objc_setAssociatedObject(self、kKVOBehaviorsKey、obs、objc_ASSOCIATION_RETAIN);
    }
    }
    //将行为和用户信息放入内容中。。。
    [obs addBehavior:block forKeyPath:keyPath userInfo:userInfo];
    //添加观察结果
    [self addObserver:obs forKeyPath:keyPath选项:选项上下文:kKVOBehaviorsKey];
    }
    -(void)removeBehaviorForKeyPath:(NSString*)键路径
    {
    KBKVOBehaviorObserver*obs=nil;
    obs=[[objc_getAssociatedObject(self,kKVOBehaviorsKey)retain]autorelease];
    //删除观察结果
    [self-removeObserver:obs forKeyPath:keyPath上下文:kkvoBehaviorKey];
    //再
    
    #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