Beobachten eines NSMutableArray zum Einfügen / Entfernen

76

Eine Klasse hat eine Eigenschaft (und Instanzvariable) vom Typ NSMutableArray mit synthetisierten Accessoren (via @property). Wenn Sie dieses Array mit folgenden Elementen beobachten:

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

Eine ObservValueForKeyPath ... -Nachricht wird nicht gesendet. Folgendes sendet jedoch die richtige Benachrichtigung:

Dies liegt daran, dass mutableArrayValueForKeyein Proxy-Objekt zurückgegeben wird, das sich um die Benachrichtigung von Beobachtern kümmert.

Aber sollten die synthetisierten Accessoren ein solches Proxy-Objekt nicht automatisch zurückgeben? Wie kann ich das umgehen [super mutableArrayValueForKey...]? Soll ich einen benutzerdefinierten Accessor schreiben, der nur aufgerufen wird ?

Adam Ernst
quelle

Antworten:

79

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

Nein.

Wie kann ich das umgehen [super mutableArrayValueForKey...]? Soll ich einen benutzerdefinierten Accessor schreiben, der nur aufgerufen wird ?

Implementieren Sie die Array-Accessoren . Wenn Sie diese aufrufen, sendet KVO die entsprechenden Benachrichtigungen automatisch. Alles was Sie tun müssen ist:

und das Richtige wird automatisch geschehen.

Der Einfachheit halber können Sie einen addTheArrayObject:Accessor schreiben . Dieser Accessor würde einen der oben beschriebenen realen Array-Accessoren aufrufen:

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

Dann [myObject insertObject:…]schreibst du stattdessen [myObject addTheArrayObject:newObject].

Leider add<Key>Object:und sein Gegenstückremove<Key>Object: werden, wie ich zuletzt überprüft habe, von KVO nur für festgelegte (wie in NSSet) Eigenschaften erkannt, nicht für Array-Eigenschaften. Sie erhalten also keine kostenlosen KVO-Benachrichtigungen, es sei denn, Sie implementieren sie über die erkannten Accessoren . Ich habe einen Fehler gemeldet: x-radar: // problem / 6407437

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

Peter Hosey
quelle
5
Das Hauptproblem ist, dass Sie beim Hinzufügen eines Beobachters eine Eigenschaft eines Objekts beobachten. Das Array ist der Wert dieser Eigenschaft, nicht die Eigenschaft selbst. Aus diesem Grund müssen Sie entweder die Accessoren oder -mutableArrayValueForKey: verwenden, um das Array zu ändern.
Chris Hanson
Ihr letzter Punkt scheint veraltet zu sein - ich erhalte kostenlose KVO-Benachrichtigungen zu NSArray-Eigenschaften, vorausgesetzt, ich implementiere sowohl das Hinzufügen als auch das Entfernen von Accessoren.
Bryan
9

Ich würde nicht verwenden willChangeValueForKeyund didChangeValueForKeyin dieser Situation. Zum einen sollen sie anzeigen, dass sich der Wert an diesem Pfad geändert hat, nicht, dass sich die Werte in einer zu vielen Beziehung ändern. Sie möchten willChange:valuesAtIndexes:forKey:stattdessen verwenden, wenn Sie dies auf diese Weise tun. Trotzdem ist die Verwendung solcher manueller KVO-Benachrichtigungen eine schlechte Kapselung. Eine bessere Möglichkeit besteht darin, eine Methode addSomeObject:in der Klasse zu definieren, die das Array tatsächlich besitzt, einschließlich der manuellen KVO-Benachrichtigungen. Auf diese Weise müssen sich externe Methoden, die Objekte zum Array hinzufügen, nicht um die Behandlung der KVO des Array-Besitzers kümmern. Dies wäre nicht sehr intuitiv und könnte zu nicht benötigtem Code und möglicherweise zu Fehlern führen, wenn Sie Objekte zum Array hinzufügen Array von mehreren Orten.

In diesem Beispiel würde ich tatsächlich weiter verwenden mutableArrayValueForKey:. Ich bin nicht positiv mit änderbaren Arrays, aber ich glaube , in die Dokumentation zu lesen , dass diese Methode tatsächlich das gesamte Array mit einem neuen Objekt ersetzt, so dass , wenn die Leistung ist ein Anliegen Sie auch umsetzen werden wollen insertObject:in<Key>AtIndex:und removeObjectFrom<Key>AtIndex:in der Klasse, die das Array besitzt .

Marc Charbonneau
quelle
7

Wenn Sie nur die geänderte Anzahl beobachten möchten, können Sie einen aggregierten Schlüsselpfad verwenden:

Beachten Sie jedoch, dass eine Neuordnung im Array nicht ausgelöst wird.

berbie
quelle
Oder Ersatz für diese Angelegenheit, z. B. wenn [-replaceObjectAtIndex: withObject:] verwendet wird.
Patrick Pijnappel
3

Ihre eigene Antwort auf Ihre eigene Frage ist fast richtig. Verkaufen Sie nicht theArrayextern. Deklarieren Sie stattdessen eine andere Eigenschaft, theMutableArraydie keiner Instanzvariablen entspricht, und schreiben Sie diesen Accessor:

Das Ergebnis ist, dass andere Objekte thisObject.theMutableArrayÄnderungen am Array vornehmen können und diese Änderungen KVO auslösen.

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

matt
quelle
Wenn ich diese Verknüpfung verwende, kann ich nur Änderungen am Schlüsselpfad "theArray" beobachten, nicht "theMutableArray".
Michael Mior
Ansonsten funktioniert alles perfekt und ich kann manipulieren, theMutableArrayals wäre es eine NSMutableArrayEigenschaft.
Michael Mior
Wenn ich versuche, dieses Proxy-Objekt zu verzögern, wird keine KVO-Benachrichtigung ausgegeben. Weißt du, warum? - (NSMutableArray *) theMutableArray {if (_theMutableArray) return _theMutableArray; _theMutableArray = [self mutableArrayValueForKey: @ "theArray"]; return _theMutableArray; }
Luong Huy Duc
2

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

Dies funktioniert, da, wenn die Array-Accessoren nicht implementiert sind und kein Setter für den Schlüssel vorhanden ist, mutableArrayValueForKey:nach einer Instanzvariablen mit dem Namen _<key>oder gesucht wird <key>. Wenn eine gefunden wird, leitet der Proxy alle Nachrichten an dieses Objekt weiter.

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

Patrick Pijnappel
quelle
Aus irgendeinem Grund funktioniert das bei mir nicht. In diesem Fall könnte ich allerdings wirklich blind sein.
Entalpi
1
Wenn Sie diese Lösung verwenden, stellen Sie sicher, dass Sie Ihre Eigenschaften als markieren, da readonlydies sonst in einer Endlosschleife stecken bleibt ...
Symaxion
0

Sie benötigen wickeln addObject:Anruf willChangeValueForKey:und didChangeValueForKey:Anrufe. Soweit ich weiß, gibt es für das NSMutableArray, das Sie ändern, keine Möglichkeit, über Beobachter Bescheid zu wissen, die seinen Besitzer beobachten.

Ben Gottlieb
quelle
0

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

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

HINWEIS: self.myArray sollte nicht null sein, sonst wird arrayByAddingObject: auch null

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

Peter Lapisu
quelle