19

NSMutableDictionaryの異なるキーの下に保存されている値の変更を監視(サブスクライブ)することは可能ですか?私の場合、サブスクリプションが開始されたときにキーはすでに存在していましたが、値が変更されたため、この場合は通知を受け取りたいと思います。通知に変更された値のキーをお願いします。

辞書キーがすべてNSStringインスタンスである場合、各キーパスを個別にサブスクライブできると思います。しかし、私のキーが文字列以外の場合はどうなりますか?その場合、私は運が悪いのでしょうか?

4

3 に答える 3

7

それは興味深い考えです。NSDictionaryまたはNSDictionaryControllerには、有望に見えるものが見つかりません。私の最初の本能は、NSMutableDictionaryの周りの構成を使用し、setObject:forKey:(およびおそらく-removeObjectForKey :)の呼び出しをインターセプトして、サブスクライバーに変更を通知することです。

NSMutableDictionaryのサブクラス化に関するCocoaWithLoveの投稿があり、そのルートを選択した場合に役立つ可能性があります。また、独自のNSMutableDictionaryサブクラスを作成しました。オープンソースコードを使用できます。

監視する必要のある特定のキーを指定できるオブザーバープロトコルを設計できます。あまり多くのコードであってはなりませんが、現時点で私が捨てる時間以上のものです。

于 2009-07-09T23:13:38.600 に答える
6

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に準拠していないため、プロパティを渡し0options、KVO機構がこれらのキーの古い値/新しい値を取得しようとしないようにしました。それは私のコードを爆破させるでしょう。

ボードを変更するもの(私の単純なクラスでは、これを行うメソッドは1つだけです)は、KVO機構を動作させるために必要な通知を発生させます。

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

出来上がり!文字列以外のキーを使用したNSMutableDictionaryのサブスクリプション!

于 2009-07-10T19:49:43.097 に答える
0

私はさまざまな理由でさまざまな方法で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プロパティにアクセスします。

于 2020-06-04T07:05:11.600 に答える