【问题标题】:Observing NSMutableDictionary changes观察 NSMutableDictionary 的变化
【发布时间】:2010-11-09 13:30:04
【问题描述】:

是否可以观察(订阅)存储在 NSMutableDictionary 中不同键下的值的更改?在我的情况下,启动订阅时密钥已经存在,但是值会发生变化,我希望在这种情况下得到通知。我想要通知中更改值的键。

我假设如果我的字典键都是 NSString 实例,我可以单独订阅每个键路径。但是如果我的键不是字符串呢?在那种情况下我运气不好?

【问题讨论】:

    标签: objective-c nsmutabledictionary


    【解决方案1】:

    这是一个有趣的想法。我在 NSDictionary 或 NSDictionaryController 中找不到任何看起来很有希望的东西。我的第一直觉是在 NSMutableDictionary 周围使用组合并拦截对 setObject:forKey: 的调用(可能还有 -removeObjectForKey:) 以通知订阅者更改。

    有一个关于子类化 NSMutableDictionary 的Cocoa With Love post,如果您选择走那条路,它可能会很有用。我也有created my own NSMutableDictionary subclasses,欢迎大家使用开源代码。

    您可以设计一个观察者协议,该协议可以指定应监控的特定键。代码不应该太多,但比我现在有时间扔掉的要多。

    【讨论】:

    • 谢谢。我认为围绕 NSMutableDictionary 的组合是要走的路。除非键是 NSStrings,否则我仍然看不到如何使其工作,但我想我可能可以使用它。
    • @Quinn Taylor:您的自定义子类的链接似乎已失效,我需要检查此代码。你还把它挂在某个地方吗?
    【解决方案2】:

    我现在已经成功地实现了这一点,使用 NSMutableDictionary 周围的组合。我很惊讶它花了这么少的代码。我实现的类是 Board,用来表示棋盘游戏中的模型。任何人都可以通过调用 addObserver: 方法订阅板状态的更改,实现如下:

    - (void)addObserver:(id)observer {
        for (id key in grid)
            [self addObserver:observer
                   forKeyPath:[key description]
                      options:0
                      context:key];
    }
    

    由于只能使用 KVO 模型订阅密钥,所以我欺骗并订阅了密钥的描述,但将密钥本身作为上下文传递。在观察我的 Board 实例的对象中,我实现了 -observeValueForKeyPath:ofObject:change:context: 以忽略 keyPath 字符串并只使用传入的上下文。

    对于我使用此方法创建的人工属性,我的简单 Board 类不符合 KVO,因此我在 options 属性中传递了 0,因此 KVO 机器不会尝试获取这些键的旧/新值.这会导致我的代码崩溃。

    任何改变板子的事情(在我的简单类中只有一种方法可以做到这一点)引发必要的通知以使 KVO 机制开始行动:

    - (void)setPiece:(id)piece atLocation:(Location*)loc {
        [self willChangeValueForKey:[loc description]];
        [grid setObject:piece forKey:loc];
        [self didChangeValueForKey:[loc description]];
    }
    

    瞧!使用非字符串键订阅 NSMutableDictionary!

    【讨论】:

      【解决方案3】:

      虽然我使用 NSMutableDictionary 的原因和方式不同(我没有继承它),但我找到了另一种方法。

      我正在使用 NSMutableDictionary,因为它可以很好地从/到 JSON 进行序列化和反序列化。要获取和设置值,我使用“包装器”。这些对象使用字典作为“原始”实体并提供获取器和设置器来访问值。然后,我的 getter 和 setter 实现只需定义键(和对象类型)。

      还有一个基类提供我将这些字典传递给(或从中获取)的属性。

      @protocol PMBase <NSObject>
      @property (nonatomic, strong, nullable) NSMutableDictionary<NSString*, id>* entity;
      @end
      
      @interface MBase : NSObject<PMBase>
      @property (nonatomic, strong, nullable) NSMutableDictionary<NSString*, id>* entity;
      @end
      
      inline static NSString* _Nullable mbase_get_string(id<PMBase> _Nonnull const obj,
                                                         NSString* _Nonnull const key,
                                                         NSString* _Nullable const fallback) {
          id const val = [obj.entity objectForKey:key];
          return [val isKindOfClass:NSString.class] ? val : fallback;
      }
      
      inline static void mbase_set_string(id<PMBase> _Nonnull const obj,
                                          NSString* _Nonnull const key,
                                          NSString* _Nullable const value) {
          if (value) {
              [obj.entity setObject:value forKey:key];
          } else {
              [obj.entity removeObjectForKey:key];
          }
      }
      
      inline static NSNumber* _Nullable mbase_get_number(id<PMBase> _Nonnull const obj,
                                                         NSString* _Nonnull const key,
                                                         NSNumber* _Nullable const fallback) {
          id const val = [obj.entity objectForKey:key];
          return [val isKindOfClass:NSNumber.class] ? val : fallback;
      }
      
      inline static void mbase_set_number(id<PMBase> _Nonnull const obj,
                                          NSString* _Nonnull const key,
                                          NSNumber* _Nullable const value) {
          if (value) {
              [obj.entity setObject:value forKey:key];
          } else {
              [obj.entity removeObjectForKey:key];
          }
      }
      
      (MBase implementation is no magic, so no code here.)
      

      在子类中,我只定义了额外的属性,在这里我覆盖了 getter 和 setter。子类几乎实现了访问器,有时,它们返回其他 MBase 子类的实例。

      @protocol PMDevice <NSObject>
      // Not important here
      @end
      
      @interface MDevice : MBase <PMDevice>
      
      @property (nonatomic, strong, nonnull) NSString* deviceName;
      @property (nonatomic, assign) MDeviceType deviceType;             // phone, tablet... 
      
      @end
      
      @implementation MDevice
      
      - (void)setDeviceName:(NSString*)name {
          mbase_set_string(self, @"name", name);
      }
      
      - (NSString*)deviceName {
          return mbase_get_string(self, @"name", NSLocalizedString(@"Unnamed", @"unnamed device placeholder"));
      }
      
      - (void)setDeviceType:(MDeviceType)deviceType {
          mbase_set_number(self, @"type", [NSNumber numberWithInt:(int)deviceType]);
      }
      
      - (MDeviceType)deviceType {
          return (MDeviceType)mbase_get_number(self, @"type", [NSNumber numberWithInt:MDeviceTypeOther]).intValue;
      }
      
      @end    
      

      现在我需要密钥,包装器用来访问特定的字典值。我只是将“最后使用的密钥”作为属性添加到我的 MBase 中,并将该行添加到 getter 和 setter 内联:

      @protocol PMBase <NSObject>
      @property (nonatomic, strong, nullable) NSString* lastUsedKey;
      @property (nonatomic, strong, nullable) NSMutableDictionary<NSString*, id>* entity;
      @end
      
      @interface MBase : NSObject<PMBase>
      @property (nonatomic, strong, nullable) NSString* lastUsedKey;
      @property (nonatomic, strong, nullable) NSMutableDictionary<NSString*, id>* entity;
      @end
      
      inline static NSString* _Nullable mbase_get_string(id<PMBase> _Nonnull const obj,
                                                         NSString* _Nonnull const key,
                                                         NSString* _Nullable const fallback) {
          obj.lastUsedKey = key;
          id const val = [obj.entity objectForKey:key];
          return [val isKindOfClass:NSString.class] ? val : fallback;
      }
      
      inline static void mbase_set_string(id<PMBase> _Nonnull const obj,
                                          NSString* _Nonnull const key,
                                          NSString* _Nullable const value) {
          obj.lastUsedKey = key;
          if (value) {
              [obj.entity setObject:value forKey:key];
          } else {
              [obj.entity removeObjectForKey:key];
          }
      }
      
      inline static NSNumber* _Nullable mbase_get_number(id<PMBase> _Nonnull const obj,
                                                         NSString* _Nonnull const key,
                                                         NSNumber* _Nullable const fallback) {
          obj.lastUsedKey = key;
          id const val = [obj.entity objectForKey:key];
          return [val isKindOfClass:NSNumber.class] ? val : fallback;
      }
      
      inline static void mbase_set_number(id<PMBase> _Nonnull const obj,
                                          NSString* _Nonnull const key,
                                          NSNumber* _Nullable const value) {
          obj.lastUsedKey = key;
          if (value) {
              [obj.entity setObject:value forKey:key];
          } else {
              [obj.entity removeObjectForKey:key];
          }
      }
      

      现在,每当我需要有关键的信息时(如果我遇到这样做的原因,键可能会更改),我将访问一个值 getter,然后访问 lastUsedKey 属性。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-08-05
        • 2018-02-26
        • 2015-09-13
        • 1970-01-01
        • 2020-07-29
        • 2021-08-30
        • 2018-12-12
        • 2021-05-03
        相关资源
        最近更新 更多