是否有可能观察(订阅)NSMutableDictionary中不同键下存储的值的更改?在我的情况下,启动订阅时键已经存在,但是值会更改,在这种情况下,我希望得到通知。我想要通知中更改值的键。
我假设如果我的字典键是所有NSString实例,我可以分别订阅每个键路径。但是,如果我的密钥是非字符串怎么办?在这种情况下,我会不走运吗?
这是一个有趣的想法。我在NSDictionary或NSDictionaryController中找不到任何有前途的东西。我的第一个直觉是在NSMutableDictionary周围使用合成,并拦截对setObject:forKey :(可能还有-removeObjectForKey :)的调用,以将更改通知订户。
关于NSMutableDictionary的子类化,有一个Cocoa With Love post,如果您选择沿这条路线走,那将很有用。我也有created my own NSMutableDictionary subclasses,欢迎您使用开源代码。
您可以设计一个观察者协议,该协议可以指定应监视的特定密钥。应该不是太多的代码,但是比我现在有时间放弃的更多。
我现在已经使用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!
尽管我出于不同的原因并且以不同的方式(我未将其分类)使用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
属性。