NSMutableDictionaryの異なるキーの下に保存されている値の変更を監視(サブスクライブ)することは可能ですか?私の場合、サブスクリプションが開始されたときにキーはすでに存在していましたが、値が変更されたため、この場合は通知を受け取りたいと思います。通知に変更された値のキーをお願いします。
辞書キーがすべてNSStringインスタンスである場合、各キーパスを個別にサブスクライブできると思います。しかし、私のキーが文字列以外の場合はどうなりますか?その場合、私は運が悪いのでしょうか?
NSMutableDictionaryの異なるキーの下に保存されている値の変更を監視(サブスクライブ)することは可能ですか?私の場合、サブスクリプションが開始されたときにキーはすでに存在していましたが、値が変更されたため、この場合は通知を受け取りたいと思います。通知に変更された値のキーをお願いします。
辞書キーがすべてNSStringインスタンスである場合、各キーパスを個別にサブスクライブできると思います。しかし、私のキーが文字列以外の場合はどうなりますか?その場合、私は運が悪いのでしょうか?
それは興味深い考えです。NSDictionaryまたはNSDictionaryControllerには、有望に見えるものが見つかりません。私の最初の本能は、NSMutableDictionaryの周りの構成を使用し、setObject:forKey:(およびおそらく-removeObjectForKey :)の呼び出しをインターセプトして、サブスクライバーに変更を通知することです。
NSMutableDictionaryのサブクラス化に関するCocoaWithLoveの投稿があり、そのルートを選択した場合に役立つ可能性があります。また、独自のNSMutableDictionaryサブクラスを作成しました。オープンソースコードを使用できます。
監視する必要のある特定のキーを指定できるオブザーバープロトコルを設計できます。あまり多くのコードであってはなりませんが、現時点で私が捨てる時間以上のものです。
NSMutableDictionaryの周りの構成を使用して、これを正常に実装しました。必要なコードが少ないことに驚いています。私が実装したクラスはボードで、ボードゲームでモデルを表すために使用されます。次のように実装されたaddObserver:メソッドを呼び出すことにより、誰でもボードの状態の変更をサブスクライブできます。
- (void)addObserver:(id)observer {
for (id key in grid)
[self addObserver:observer
forKeyPath:[key description]
options:0
context:key];
}
KVOモデルを使用してのみキーをサブスクライブできるため、キーの説明をだましてサブスクライブしましたが、キー自体をコンテキストとして渡しました。ボードのインスタンスを監視するオブジェクトでは-observeValueForKeyPath:ofObject:change:context:、keyPath文字列を無視し、渡されたコンテキストを使用するように実装します。
私の単純なBoardクラスは、このメソッドを使用して作成した人工プロパティに対してKVOに準拠していないため、プロパティを渡し0てoptions、KVO機構がこれらのキーの古い値/新しい値を取得しようとしないようにしました。それは私のコードを爆破させるでしょう。
ボードを変更するもの(私の単純なクラスでは、これを行うメソッドは1つだけです)は、KVO機構を動作させるために必要な通知を発生させます。
- (void)setPiece:(id)piece atLocation:(Location*)loc {
[self willChangeValueForKey:[loc description]];
[grid setObject:piece forKey:loc];
[self didChangeValueForKey:[loc description]];
}
出来上がり!文字列以外のキーを使用したNSMutableDictionaryのサブスクリプション!
私はさまざまな理由でさまざまな方法でNSMutableDictionaryを使用していますが(私はそれをサブクラス化していない)、別のアプローチを見つけました。
NSMutableDictionaryを使用しています。これは、JSONとの間で適切にシリアル化および逆シリアル化できるためです。値を取得して設定するには、「ラッパー」を使用しています。これらは、ディクショナリを「生の」エンティティとして使用し、値にアクセスするためのゲッターとセッターを提供するオブジェクトです。次に、ゲッターとセッターの実装は、キー(およびオブジェクトタイプ)を定義するだけです。
これらの辞書を渡す(または取得する)プロパティを提供する基本クラスもあります。
@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.)
サブクラスでは、追加のプロパティを定義するだけで、ゲッターとセッターをオーバーライドします。サブクラスはほとんどアクセサーを実装し、場合によっては他の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に追加し、その行をゲッターとセッターのインラインに追加しました。
@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プロパティにアクセスします。