Wie kann ich feststellen, ob an ein Objekt ein Schlüsselwertbeobachter angehängt ist?

142

Wenn Sie einem objektiven c-Objekt anweisen, Observer zu entfernen: Wenn ein Schlüsselpfad und dieser Schlüsselpfad nicht registriert wurden, werden die Sads geknackt. mögen -

'Ein Beobachter für den Schlüsselpfad "theKeyPath" kann nicht entfernt werden, da er nicht als Beobachter registriert ist.'

Gibt es eine Möglichkeit festzustellen, ob ein Objekt einen registrierten Beobachter hat, damit ich dies tun kann?

if (object has observer){
  remove observer
}
else{
  go on my merry way
}
Aran Mulholland
quelle
Ich bin in dieses Szenario geraten, als ich eine alte App unter iOS 8 aktualisiert habe, bei der ein View Controller freigegeben wurde, und die Ausnahme "Kann nicht entfernen" ausgelöst wurde. Ich dachte , dass durch den Aufruf addObserver:in viewWillAppear:und entsprechend removeObserver:in viewWillDisappear:wurden die Anrufe korrekt gepaart. Ich muss eine schnelle Lösung finden, damit ich die Try-Catch-Lösung implementieren und einen Kommentar hinterlassen kann, um die Ursache weiter zu untersuchen.
Bneely
Ich habe es nur mit etwas Ähnlichem zu tun und sehe, dass ich mein Design genauer untersuchen und anpassen muss, damit ich den Beobachter nicht erneut entfernen muss.
Bogdan
Die Verwendung eines Bool-Werts wie in dieser Antwort vorgeschlagen hat für mich am besten funktioniert
Lance Samaria

Antworten:

315

Versuchen Sie es mit Ihrem removeObserver-Aufruf

@try{
   [someObject removeObserver:someObserver forKeyPath:somePath];
}@catch(id anException){
   //do nothing, obviously it wasn't attached because an exception was thrown
}
Adam
quelle
12
1+ Gute Antwort, hat für mich funktioniert und ich stimme Ihrer Beschimpfung zu, bevor sie bearbeitet wurde.
Robert
25
für gelöschte Beschimpfungen gestimmt, denen ich höchstwahrscheinlich zustimmen würde.
Ben Gotow
12
Gibt es hier keine andere elegante Lösung? Dieser dauert mindestens 2 ms pro Nutzung ... stellen Sie es sich in einer Tabellenzelle vor
João Nunes
19
Downvoted, weil Sie nicht sagen, dass dies für den Produktionscode unsicher ist und wahrscheinlich jederzeit fehlschlägt. Das Auslösen von Ausnahmen durch Framework-Code ist in Cocoa keine Option.
Nikolai Ruhe
6
Verwendung dieses Codes in Swift 2.1. do {try self.playerItem? .removeObserver (self, forKeyPath: "status")} let let error als NSError {print (error.localizedDescription)}, der eine Warnung erhält.
Vipulk617
37

Die eigentliche Frage ist, warum Sie nicht wissen, ob Sie es beobachten oder nicht.

Wenn Sie dies in der Klasse des beobachteten Objekts tun, stoppen Sie. Was auch immer es beobachtet, erwartet, es weiter zu beobachten. Wenn Sie die Benachrichtigungen des Beobachters ohne dessen Wissen abschneiden, müssen Sie damit rechnen, dass die Dinge kaputt gehen. Erwarten Sie insbesondere, dass der Status des Beobachters veraltet ist, da er keine Aktualisierungen vom zuvor beobachteten Objekt erhält.

Wenn Sie dies in der Klasse des Beobachtungsobjekts tun, merken Sie sich einfach, welche Objekte Sie beobachten (oder, wenn Sie immer nur ein Objekt beobachten, ob Sie es beobachten). Dies setzt voraus, dass die Beobachtung dynamisch ist und zwischen zwei ansonsten nicht verwandten Objekten liegt; Wenn der Beobachter das Beobachtete besitzt, fügen Sie den Beobachter einfach hinzu, nachdem Sie das Beobachtete erstellt oder beibehalten haben, und entfernen Sie den Beobachter, bevor Sie das Beobachtete freigeben.

Das Hinzufügen und Entfernen eines Objekts als Beobachter sollte normalerweise in der Klasse des Beobachters und niemals in der Klasse des beobachteten Objekts erfolgen.

Peter Hosey
quelle
14
Anwendungsfall: Sie möchten Beobachter in viewDidUnload und auch in dealloc entfernen. Dadurch werden sie zweimal entfernt und die Ausnahme wird ausgelöst, wenn Ihr viewController aus einer Speicherwarnung entladen und dann ebenfalls freigegeben wird. Wie schlagen Sie vor, mit diesem Szenario umzugehen?
Bandejapaisa
2
@bandejapaisa: So ziemlich das, was ich in meiner Antwort gesagt habe: Behalte im Auge, ob ich beobachte und versuche nur, mit dem Beobachten aufzuhören, wenn ich es bin.
Peter Hosey
41
Nein, das ist keine interessante Frage. Sie sollten dies nicht im Auge behalten müssen; Sie sollten in der Lage sein, die Registrierung aller Listener im Dealloc einfach aufzuheben, ohne sich darum zu kümmern, ob Sie zufällig den Codepfad erreicht haben, in dem er hinzugefügt wurde, oder nicht. Es sollte wie der removeObserver von NSNotificationCenter funktionieren, was egal ist, ob Sie tatsächlich einen haben oder nicht. Diese Ausnahme erzeugt einfach Fehler, bei denen sonst keine existieren würden, was ein schlechtes API-Design ist.
Glenn Maynard
1
@GlennMaynard: Wie ich in der Antwort sagte: „Wenn Sie die Benachrichtigungen des Beobachters ohne dessen Wissen abschneiden, erwarten Sie, dass die Dinge kaputt gehen. Erwarten Sie insbesondere, dass der Status des Beobachters veraltet ist, da er keine Aktualisierungen von dem zuvor beobachteten Objekt erhält. “ Jeder Beobachter sollte seine eigene Beobachtung beenden; Andernfalls sollte es idealerweise gut sichtbar sein.
Peter Hosey
3
Nichts in der Frage spricht davon, die Beobachter anderer Codes zu entfernen .
Glenn Maynard
25

FWIW [someObject observationInfo]scheint zu sein, nilwenn someObjectes keine Beobachter gibt. Ich würde diesem Verhalten jedoch nicht vertrauen, da ich es nicht dokumentiert gesehen habe. Außerdem kann ich nicht lesen observationInfo, um bestimmte Beobachter zu finden.

ma11hew28
quelle
Wissen Sie zufällig, wie ich einen bestimmten Beobachter abrufen kann? objectAtIndex:liefert nicht das gewünschte Ergebnis.)
Eimantas
1
@MattDiPasquale Wissen Sie, wie ich ObservationInfo im Code lesen kann? In Drucken kommt es gut heraus, aber es ist ein Zeiger auf Leere. Wie soll ich es lesen?
Neeraj
ObservationInfo ist eine Debugging-Methode, die im Debugging-Dokument von Xcode dokumentiert ist (etwas mit "Magie" im Titel). Sie können versuchen, es nachzuschlagen. Ich kann Ihnen sagen, dass Sie etwas falsch machen, wenn Sie wissen müssen, ob jemand Ihr Objekt beobachtet. Überdenken Sie Ihre Architektur und Logik. Ich habe es auf die harte
Tour
Quelle:NSKeyValueObserving.h
Nefarianblack
plus 1 für eine komische Sackgasse, aber immer noch etwas hilfreiche Antwort
Will Von Ullrich
4

Die einzige Möglichkeit, dies zu tun, besteht darin, ein Flag zu setzen, wenn Sie einen Beobachter hinzufügen.

Leibowitzn
quelle
3
Wenn Sie überall BOOLs haben, erstellen Sie besser noch ein KVO-Wrapper-Objekt, das das Hinzufügen und Entfernen des Beobachters übernimmt. Es kann sicherstellen, dass Ihr Beobachter nur einmal entfernt wird. Wir haben ein Objekt wie dieses verwendet und es funktioniert.
Bandejapaisa
Tolle Idee, wenn Sie nicht immer beobachten.
Andre Simon
4

Wenn Sie einem Objekt einen Beobachter hinzufügen, können Sie ihn NSMutableArraywie folgt hinzufügen :

- (void)addObservedObject:(id)object {
    if (![_observedObjects containsObject:object]) {
        [_observedObjects addObject:object];
    }
}

Wenn Sie die Objekte nicht beobachten möchten, können Sie Folgendes tun:

for (id object in _observedObjects) {
    if ([object isKindOfClass:[MyClass class]]) {
        MyClass *myObject = (MyClass *)object;
        [self unobserveMethod:myObject];
    }
}
[_observedObjects removeAllObjects];

Denken Sie daran, wenn Sie ein einzelnes Objekt nicht beobachten, entfernen Sie es aus dem _observedObjectsArray:

- (void)removeObservedObject:(id)object {
    if ([_observedObjects containsObject:object]) {
        [_observedObjects removeObject:object];
    }
}
Oritm
quelle
1
Wenn dies in einer Welt mit mehreren Threads passiert, müssen Sie sicherstellen, dass Ihr Array ThreadSafe
shrutim
Sie behalten eine starke Referenz eines Objekts bei, wodurch sich die Anzahl der Aufbewahrungen jedes Mal erhöht, wenn ein Objekt zur Liste hinzugefügt wird, und die Zuordnung nicht aufgehoben wird, es sei denn, die Referenz wird aus dem Array entfernt. Ich würde es vorziehen zu verwendenNSHashTable / NSMapTablezu verwenden, um die schwachen Referenzen zu behalten.
Atulkhatri
3

Meiner Meinung nach funktioniert dies ähnlich wie der RetainCount-Mechanismus. Sie können nicht sicher sein, ob Sie im Moment Ihren Beobachter haben. Selbst wenn Sie Folgendes überprüfen: self.observationInfo - Sie können nicht sicher sein, ob Sie in Zukunft Beobachter haben / nicht haben werden.

Wie RetainCount . Vielleicht ist die ObservationInfo- Methode nicht genau so nutzlos, aber ich verwende sie nur für Debug-Zwecke.

Als Ergebnis müssen Sie dies nur wie bei der Speicherverwaltung tun. Wenn Sie einen Beobachter hinzugefügt haben, entfernen Sie ihn einfach, wenn Sie ihn nicht benötigen. Wie bei der Verwendung der Methoden viewWillAppear / viewWillDisappear usw. Z.B:

-(void) viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self addObserver:nil forKeyPath:@"" options:NSKeyValueObservingOptionNew context:nil];
}

-(void) viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    [self removeObserver:nil forKeyPath:@""];
}

Und wenn Sie einige spezifische Prüfungen benötigen, implementieren Sie Ihre eigene Klasse, die eine Reihe von Beobachtern verarbeitet, und verwenden Sie sie für Ihre Prüfungen.

quarezz
quelle
[self removeObserver:nil forKeyPath:@""]; muss vorher gehen: [super viewWillDisappear:animated];
Joshua Hart
@ JoshuaHart warum?
Quarezz
Weil es eine Abreißmethode (Dealloc) ist. Wenn Sie eine Art Teardown-Methode überschreiben, rufen Sie super last auf. Wie: - (void) setupSomething { [super setupSomething]; … } - (void) tearDownSomething { … [super tearDownSomething]; }
Joshua Hart
viewWillDisapear ist keine Abreißmethode und hat keine Verbindung zu Dealloc. Wenn Sie zum Navigationsstapel vorrücken, wird viewWillDisapear aufgerufen, Ihre Ansicht bleibt jedoch im Speicher. Ich sehe, wohin Sie mit der Logik des Auf- und Abbaus gehen, aber wenn Sie dies hier tun, wird dies keinen wirklichen Nutzen bringen. Sie möchten das Entfernen nur dann vor Super platzieren, wenn Sie eine Logik in der Basisklasse haben, die mit dem aktuellen Beobachter in Konflikt stehen könnte.
Quarezz
3

[someObject observationInfo]Rückkehr, nilwenn kein Beobachter anwesend ist.

if ([tableMessage observationInfo] == nil)
{
   NSLog(@"add your observer");
}
else
{
  NSLog(@"remove your observer");

}
Anupama
quelle
Entsprechend den Apple-Dokumenten: ObservationInfo gibt einen Zeiger zurück, der Informationen zu allen Beobachtern identifiziert, die beim Empfänger registriert sind.
FredericK
Dies wurde besser gesagt in @ mattdipasquale Antwort
Ben Leggiero
2

Der springende Punkt des Beobachtermusters ist, dass eine beobachtete Klasse "versiegelt" werden kann - um nicht zu wissen oder sich darum zu kümmern, ob sie beobachtet wird. Sie versuchen explizit, dieses Muster zu brechen.

Warum?

Das Problem, das Sie haben, ist, dass Sie davon ausgehen, dass Sie beobachtet werden, wenn Sie es nicht sind. Dieses Objekt hat die Beobachtung nicht gestartet. Wenn Sie möchten, dass Ihre Klasse die Kontrolle über diesen Prozess hat, sollten Sie das Benachrichtigungscenter verwenden. Auf diese Weise hat Ihre Klasse die volle Kontrolle darüber, wann Daten beobachtet werden können. Daher ist es egal, wer zuschaut.

Adonoho
quelle
10
Er fragt, wie der Hörer herausfinden kann, ob er etwas hört, und nicht, wie das beobachtete Objekt herausfinden kann, ob es beobachtet wird.
Glenn Maynard
1

Ich bin kein Fan dieser Try-Catch-Lösung. Was ich also meistens mache, ist, dass ich eine Methode zum Abonnieren und Abbestellen für eine bestimmte Benachrichtigung innerhalb dieser Klasse erstelle. Mit diesen beiden Methoden können Sie das Objekt beispielsweise für die globale Tastaturbenachrichtigung abonnieren oder abbestellen:

@interface ObjectA : NSObject
-(void)subscribeToKeyboardNotifications;
-(void)unsubscribeToKeyboardNotifications;
@end

Innerhalb dieser Methoden verwende ich eine private Eigenschaft, die je nach Abonnementstatus wie folgt auf true oder false gesetzt ist:

@interface ObjectA()
@property (nonatomic,assign) BOOL subscribedToKeyboardNotification
@end

@implementation

-(void)subscribeToKeyboardNotifications {
    if (!self.subscribedToKeyboardNotification) {
        [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(onKeyboardShow:) name:UIKeyboardWillShowNotification object:nil];
        [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(onKeyboardHide:) name:UIKeyboardWillHideNotification object:nil];
        self.subscribedToKeyboardNotification = YES;
    }
}

-(void)unsubscribeToKeyboardNotifications {
    if (self.subscribedToKeyboardNotification) {
        [[NSNotificationCenter defaultCenter]removeObserver:self name:UIKeyboardWillShowNotification object:nil];
        [[NSNotificationCenter defaultCenter]removeObserver:self name:UIKeyboardWillHideNotification object:nil];
        self.subscribedToKeyboardNotification = NO;
    }
}
@end
Sebastian Boldt
quelle
0

Zusätzlich zu Adams Antwort möchte ich vorschlagen, ein solches Makro zu verwenden

#define SafeRemoveObserver(sender, observer, keyPath) \
@try{\
   [sender removeObserver:observer forKeyPath:keyPath];\
}@catch(id anException){\
}

Anwendungsbeispiel

- (void)dealloc {
    SafeRemoveObserver(someObject, self, somePath);
}
Wattson
quelle
1
Wie verrückt ist es, dass es eine Ausnahme auslöst? Warum macht es nicht einfach nichts, wenn nichts angehängt ist?
Aran Mulholland