it-swarm.com.de

Beobachten eines NSMutableArray zum Einfügen/Entfernen

Eine Klasse hat eine Eigenschaft (und eine Instanzvariable) vom Typ NSMutableArray mit synthetisierten Zugriffsmethoden (über @property). Wenn Sie dieses Array mit folgendem beobachten:

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

Fügen Sie dann ein Objekt wie folgt in das Array ein:

[myObj.theArray addObject:NSString.string];

Eine ObservValueForKeyPath-Benachrichtigung wird nicht gesendet. Das folgende sendet jedoch die richtige Benachrichtigung:

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

Dies liegt daran, dass mutableArrayValueForKey ein Proxy-Objekt zurückgibt, das sich um die Benachrichtigung von Beobachtern kümmert.

Aber sollten die synthetisierten Accessoren nicht automatisch ein solches Proxy-Objekt zurückgeben? Wie kann man dies umgehen - sollte ich einen benutzerdefinierten Accessor schreiben, der einfach [super mutableArrayValueForKey...] aufruft?

73
Adam Ernst

Aber sollten die synthetisierten Accessoren nicht automatisch ein solches Proxy-Objekt zurückgeben?

Nein.

Wie kann man dies umgehen - sollte ich einen benutzerdefinierten Accessor schreiben, der einfach [super mutableArrayValueForKey...] aufruft?

Implementieren Sie die Array-Zugriffsmethoden . Wenn Sie diese anrufen, sendet KVO die entsprechenden Benachrichtigungen automatisch. Sie müssen also nur noch Folgendes tun:

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

und das Richtige geschieht automatisch.

Der Einfachheit halber können Sie einen addTheArrayObject:-Accessor schreiben. Dieser Accessor ruft einen der oben beschriebenen realen Array-Accessor auf:

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

(Sie können und sollten anstelle von NSObject die richtige Klasse für die Objekte im Array eingeben.)

Dann schreiben Sie statt [myObject insertObject:…][myObject addTheArrayObject:newObject].

Leider werden add<Key>Object: und sein Gegenstück remove<Key>Object:, als letzte überprüft, nur von KVO für Set-Eigenschaften (wie in NSSet) und nicht für Array-Eigenschaften erkannt. Sie erhalten also keine kostenlosen KVO-Benachrichtigungen, wenn Sie sie nicht zusätzlich zu Accessoren implementieren erkennt es. Ich habe einen Fehler darüber eingereicht: x-radar: // problem/6407437

Ich habe eine Liste aller Accessor-Auswahlformate in meinem Blog.

78
Peter Hosey

Ich würde willChangeValueForKey und didChangeValueForKey in dieser Situation nicht verwenden. Zum einen sollen sie darauf hinweisen, dass sich der Wert an diesem Pfad geändert hat und nicht, dass sich die Werte in einer zu vielen Beziehung ändern. Sie möchten stattdessen willChange:valuesAtIndexes:forKey: verwenden, wenn Sie dies auf diese Weise tun würden. Die Verwendung manueller KVO-Benachrichtigungen wie diese ist jedoch eine schlechte Verkapselung. Eine bessere Methode ist die Definition einer Methode addSomeObject: in der Klasse, die das Array tatsächlich besitzt. Dazu gehören die manuellen KVO-Benachrichtigungen. Auf diese Weise müssen externe Methoden, die Objekte zu dem Array hinzufügen, sich auch nicht um den Umgang mit dem KVO des Array-Besitzers kümmern, was nicht sehr intuitiv ist und zu unnötigem Code und möglicherweise zu Fehlern führen könnte, wenn Sie dem Array Objekte hinzufügen Array von mehreren Stellen.

In diesem Beispiel würde ich mutableArrayValueForKey: tatsächlich weiterhin verwenden. Ich kann die veränderlichen Arrays nicht positiv beurteilen, aber ich glaube aus dem Lesen der Dokumentation, dass diese Methode tatsächlich das gesamte Array durch ein neues Objekt ersetzt. Wenn Leistung ein Problem darstellt, sollten Sie auch insertObject:in<Key>AtIndex: und removeObjectFrom<Key>AtIndex: in der Klasse implementieren, die sie besitzt das Array.

9

wenn Sie nur die Anzahl der Änderungen beobachten möchten, können Sie einen Aggregatschlüsselpfad verwenden:

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

seien Sie sich jedoch bewusst, dass eine Neuordnung im Array nicht ausgelöst wird.

7
berbie

Ihre eigene Antwort auf Ihre eigene Frage ist fast richtig. theArray nicht extern verkaufen. Deklarieren Sie stattdessen eine andere Eigenschaft, theMutableArray, die keiner Instanzvariablen entspricht, und schreiben Sie diesen Zugriffsmechanismus:

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

Das Ergebnis ist, dass andere Objekte thisObject.theMutableArray verwenden können, um Änderungen am Array vorzunehmen, und diese Änderungen lösen KVO aus.

Die anderen Antworten, die darauf hinweisen, dass die Effizienz erhöht wird, wenn Sie auch insertObject:inTheArrayAtIndex: und removeObjectFromTheArrayAtIndex: implementieren, sind noch korrekt. Es ist jedoch nicht erforderlich, dass andere Objekte über diese Objekte Bescheid wissen oder sie direkt aufrufen müssen.

3
matt

Wenn Sie den Setter nicht benötigen, können Sie auch das einfachere Formular verwenden, das eine ähnliche Leistung (gleiche Wachstumsrate in meinen Tests) und weniger Heizplatte aufweist.

// 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"

Dies funktioniert, weil mutableArrayValueForKey: eine Instanzvariable mit dem Namen _<key> oder <key> sucht, wenn die Array-Zugriffsmethoden nicht implementiert sind und kein Schlüssel für den Schlüssel vorhanden ist. Wenn er eine findet, leitet der Proxy alle Nachrichten an dieses Objekt weiter.

Siehe diese Apple-Dokumente , Abschnitt "Accessor-Suchmuster für bestellte Sammlungen", Nr. 3.

2

eine Lösung besteht darin, ein NSArray zu verwenden und es durch Einfügen und Entfernen wie von Grund auf neu zu erstellen

- (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;
}

als Sie die KVO bekommen und alte und neue Array vergleichen können

ANMERKUNG: self.myArray sollte nicht gleich Null sein, andernfalls führt auch ArrayByAddingObject: zu Null

Je nach Fall ist dies möglicherweise die Lösung. Da NSArray nur Zeiger speichert, ist dies kein Overhead, es sei denn, Sie arbeiten mit großen Arrays und häufigen Operationen

0
Peter Lapisu

Sie müssen Ihren addObject:-Aufruf in willChangeValueForKey:- und didChangeValueForKey:-Aufrufe einschließen. Soweit ich weiß, gibt es keine Möglichkeit für den NSMutableArray, den Sie ändern, um zu erfahren, dass Beobachter seinen Besitzer beobachten.

0
Ben Gottlieb