web-dev-qa-db-ja.com

挿入/削除のためのNSMutableArrayの観察

クラスには、(@propertyを介して)合成されたアクセサーを持つNSMutableArray型のプロパティ(およびインスタンス変数)があります。次を使用してこの配列を観察する場合:

[myObj addObserver:self forKeyPath:@"theArray" options:0 context:NULL];

そして、次のように配列にオブジェクトを挿入します。

[myObj.theArray addObject:NSString.string];

ObserveValueForKeyPath ...通知はnotが送信されます。ただし、以下は適切な通知を送信します。

[[myObj mutableArrayValueForKey:@"theArray"] addObject:NSString.string];

これは、mutableArrayValueForKeyがオブザーバーへの通知を処理するプロキシオブジェクトを返すためです。

しかし、合成されたアクセサは、そのようなプロキシオブジェクトを自動的に返すべきではありませんか?これを回避する適切な方法は何ですか?[super mutableArrayValueForKey...]を呼び出すだけのカスタムアクセサを作成する必要がありますか?

74
Adam Ernst

しかし、合成されたアクセサは、そのようなプロキシオブジェクトを自動的に返すべきではありませんか?

番号。

これを回避する適切な方法は何ですか?[super mutableArrayValueForKey...]を呼び出すだけのカスタムアクセサを作成する必要がありますか?

いいえ。 array accessors を実装します。これらを呼び出すと、KVOは適切な通知を自動的に投稿します。だからあなたがしなければならないのは:

[myObject insertObject:newObject inTheArrayAtIndex:[myObject countOfTheArray]];

そして、正しいことは自動的に起こります。

便宜上、addTheArrayObject:アクセサーを作成できます。このアクセサーは、上記の実際の配列アクセサーのいずれかを呼び出します。

- (void) addTheArrayObject:(NSObject *) newObject {
    [self insertObject:newObject inTheArrayAtIndex:[self countOfTheArray]];
}

NSObjectの代わりに、配列内のオブジェクトの適切なクラスを埋めることができます。)

次に、[myObject insertObject:…]の代わりに、[myObject addTheArrayObject:newObject]と記述します。

悲しいことに、add<Key>Object:とそれに対応するremove<Key>Object:は、最後にチェックしたのは、配列プロパティではなく、(NSSetのように)設定プロパティのKVOによってのみ認識されるため、認識されるアクセサの上に実装します。これに関するバグを報告しました:x-radar:// problem/6407437

私のブログには すべてのアクセサーセレクター形式のリスト があります。

79
Peter Hosey

この状況では、willChangeValueForKeydidChangeValueForKeyを使用しません。 1つは、そのパスの値が変更されたことを示すためのものであり、多対多の関係の値が変更されていることではありません。この方法で行った場合は、代わりにwillChange:valuesAtIndexes:forKey:を使用する必要があります。それでも、このような手動のKVO通知を使用すると、カプセル化が不適切になります。より良い方法は、実際に配列を所有するクラスでメソッドaddSomeObject:を定義することです。これには、手動のKVO通知が含まれます。このように、オブジェクトを配列に追加する外部メソッドは、配列所有者のKVOの処理についても心配する必要がありません。これは、非常に直感的ではなく、いくつかの場所からの配列。

この例では、実際にmutableArrayValueForKey:を使用し続けます。私は可変配列については積極的ではありませんが、ドキュメントを読むと、このメソッドは実際に配列全体を新しいオブジェクトに置き換えると信じているので、パフォーマンスが懸念される場合は、所有するクラスにinsertObject:in<Key>AtIndex:およびremoveObjectFrom<Key>AtIndex:配列。

9

カウントの変更を確認するだけの場合は、集約キーパスを使用できます。

[myObj addObserver:self forKeyPath:@"theArray.@count" options:0 context:NULL];

ただし、theArrayの並べ替えは発生しません。

7
berbie

あなた自身の質問に対するあなた自身の答えはほとんど正しいです。 theArrayを外部で販売しないでください。代わりに、インスタンス変数なしに対応する別のプロパティtheMutableArrayを宣言し、このアクセサーを記述します。

- (NSMutableArray*) theMutableArray {
    return [self mutableArrayValueForKey:@"theArray"];
}

その結果、他のオブジェクトがthisObject.theMutableArrayを使用して配列を変更でき、これらの変更がKVOをトリガーします。

insertObject:inTheArrayAtIndex:removeObjectFromTheArrayAtIndex:を実装した場合でも効率が向上することを指摘する他の回答は依然として正しいです。しかし、他のオブジェクトがこれらについて知ったり、直接呼び出したりする必要はありません。

3
matt

セッターが必要ない場合は、以下のより簡単なフォームを使用することもできます。これは、同様のパフォーマンス(私のテストでは 成長率 )であり、定型的です。

// Interface
@property (nonatomic, strong, readonly) NSMutableArray *items;

// Implementation
@synthesize items = _items;

- (NSMutableArray *)items
{
    return [self mutableArrayValueForKey:@"items"];
}

// Somewhere else
[myObject.items insertObject:@"test"]; // Will result in KVO notifications for key "items"

配列アクセサが実装されておらず、キーのセッターがない場合、mutableArrayValueForKey:_<key>または<key>という名前のインスタンス変数を検索するため、これは機能します。見つかった場合、プロキシはすべてのメッセージをこのオブジェクトに転送します。

これらのApple docs 、「順序付きコレクションのアクセサ検索パターン」、#3を参照してください。

2

1つの解決策は、NSArrayを使用し、次のように挿入および削除することにより、ゼロから作成することです。

- (void)addSomeObject:(id)object {
    self.myArray = [self.myArray arrayByAddingObject:object];
}

- (void)removeSomeObject:(id)object {
    NSMutableArray * ma = [self.myArray mutableCopy];
    [ma removeObject:object];
    self.myArray = ma;
}

kVOを取得して、古いアレイと新しいアレイを比較できるよりも

注:self.myArrayはnilであってはなりません。そうでない場合、arrayByAddingObject:はnilになります。

NS

0
Peter Lapisu

addObject:呼び出しをwillChangeValueForKey:およびdidChangeValueForKey:呼び出しでラップする必要があります。私の知る限り、NSMutableArrayが所有者を見ているオブザーバーについて変更する方法はありません。

0
Ben Gottlieb