观察NSMutableDictionary更改

问题描述 投票:19回答:3

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

我假设如果我的字典键是所有NSString实例,我可以分别订阅每个键路径。但是,如果我的密钥是非字符串怎么办?在这种情况下,我会不走运吗?

objective-c nsmutabledictionary
3个回答
7
投票

这是一个有趣的想法。我在NSDictionary或NSDictionaryController中找不到任何有前途的东西。我的第一个直觉是在NSMutableDictionary周围使用合成,并拦截对setObject:forKey :(可能还有-removeObjectForKey :)的调用,以将更改通知订户。

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

您可以设计一个观察者协议,该协议可以指定应监视的特定密钥。应该不是太多的代码,但是比我现在有时间放弃的更多。


6
投票

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

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

由于您只能使用KVO模型订阅密钥,因此我欺骗并订阅了密钥的description,但是将密钥本身作为上下文进行了传递。在观察我的董事会实例的对象中,我实现了-observeValueForKeyPath:ofObject:change:context:来忽略keyPath字符串,而仅使用传入的上下文。

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

改变董事会的任何事情(在我的简单班级中,只有一种方法可以这样做)引发必要的通知,以使KVO机制迅速付诸行动:

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

Voila!订阅带有非字符串键的NSMutableDictionary!


0
投票

尽管我出于不同的原因并且以不同的方式(我未将其分类)使用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];
    }
}

现在,每当我需要有关键的信息时(如果碰巧需要更改键,我都会去访问值获取器,然后再访问lastUsedKey属性。

© www.soinside.com 2019 - 2024. All rights reserved.