Jak mogę stwierdzić, czy obiekt ma dołączony obserwator wartości klucza


142

jeśli powiesz obiektowi c, aby usunął Observers: dla ścieżki klucza i ta ścieżka klucza nie została zarejestrowana, złamie to sads. lubić -

„Nie można usunąć obserwatora ze ścieżki klucza„ theKeyPath ”, ponieważ nie jest on zarejestrowany jako obserwator.”

czy istnieje sposób określenia, czy obiekt ma zarejestrowanego obserwatora, więc mogę to zrobić

if (object has observer){
  remove observer
}
else{
  go on my merry way
}

Dostałem się do tego scenariusza, aktualizując starą aplikację na iOS 8, w której kontroler widoku był zwalniany i rzucał wyjątek „Nie można usunąć”. Myślałem, że dzwoniąc addObserver:na viewWillAppear:i odpowiednio removeObserver:się viewWillDisappear:, że połączenia zostały prawidłowo połączone. Muszę szybko naprawić, więc zaimplementuję rozwiązanie try-catch i zostawię komentarz, aby dokładniej zbadać przyczynę.
bneely

Po prostu mam do czynienia z czymś podobnym i widzę, że muszę dokładniej przyjrzeć się mojemu projektowi i dostosować go, aby nie trzeba było ponownie usuwać obserwatora.
Bogdan,

użycie wartości bool, jak sugerowano w tej odpowiedzi, działało najlepiej dla mnie: stackoverflow.com/a/37641685/4833705
Lance Samaria

Odpowiedzi:


315

Spróbuj złapać wokół wywołania removeObserver

@try{
   [someObject removeObserver:someObserver forKeyPath:somePath];
}@catch(id anException){
   //do nothing, obviously it wasn't attached because an exception was thrown
}

12
1+ Dobra odpowiedź, zadziałała dla mnie i zgadzam się z twoim rantem, zanim został zredagowany.
Robert

25
głosowano za usuniętym rantem, z którym najprawdopodobniej się zgodzę.
Ben Gotow

12
Czy nie ma tu żadnego innego eleganckiego rozwiązania? ten zajmuje co najmniej 2 ms na użycie ... wyobraź sobie to w tableviewcell
João Nunes

19
Obniżony, ponieważ nie mówisz, że jest to niebezpieczne dla kodu produkcyjnego i prawdopodobnie zawiedzie w dowolnym momencie. Zgłaszanie wyjątków za pomocą kodu struktury nie jest opcją w kakao.
Nikolai Ruhe

6
Jak używać tego kodu w Swift 2.1. do {try self.playerItem? .removeObserver (self, forKeyPath: "status")} catch let error as NSError {print (error.localizedDescription)} otrzymuje ostrzeżenie.
Vipulk617

37

Prawdziwe pytanie brzmi: dlaczego nie wiesz, czy to obserwujesz, czy nie.

Jeśli robisz to w klasie obserwowanego obiektu, przestań. Cokolwiek obserwuje, oczekuje, że będzie to obserwować. Jeśli odetniesz powiadomienia obserwatora bez jego wiedzy, spodziewaj się, że coś się zepsuje; dokładniej, spodziewaj się, że stan obserwatora zgaśnie, ponieważ nie otrzyma on aktualizacji z poprzednio obserwowanego obiektu.

Jeśli robisz to w klasie obserwowanego obiektu, po prostu pamiętaj, które obiekty obserwujesz (lub, jeśli kiedykolwiek obserwujesz tylko jeden obiekt, czy go obserwujesz). Zakłada się, że obserwacja jest dynamiczna i między dwoma inaczej niepowiązanymi obiektami; jeśli obserwator jest właścicielem obserwowanego, po prostu dodaj obserwatora po utworzeniu lub zachowaniu obserwowanego i usuń obserwatora przed zwolnieniem obserwowanego.

Dodawanie i usuwanie obiektu jako obserwatora powinno zwykle mieć miejsce w klasie obserwatora, a nigdy w klasie obserwowanego obiektu.


14
Przykład zastosowania: chcesz usunąć obserwatorów w viewDidUnload, a także w dealloc. Spowoduje to dwukrotne ich usunięcie i zgłosi wyjątek, jeśli viewController zostanie zwolniony z ostrzeżenia pamięci, a następnie również zwolniony. Jak radzisz sobie z tym scenariuszem?
bandejapaisa

2
@bandejapaisa: Prawie to, co powiedziałem w mojej odpowiedzi: Śledź, czy obserwuję i staraj się przestać obserwować tylko wtedy, gdy tak.
Peter Hosey,

41
Nie, to nie jest interesujące pytanie. Nie powinieneś tego śledzić; powinieneś być w stanie po prostu wyrejestrować wszystkich słuchaczy w dealloc, bez dbania o to, czy trafiłeś na ścieżkę kodu, w której został dodany, czy nie. Powinien działać jak removeObserver NSNotificationCenter, który nie obchodzi, czy faktycznie go masz, czy nie. Ten wyjątek polega po prostu na tworzeniu błędów, których w przeciwnym razie nie byłoby, co jest złym projektem API.
Glenn Maynard,

1
@GlennMaynard: Tak jak powiedziałem w odpowiedzi: „Jeśli wyłączysz powiadomienia obserwatora bez jego wiedzy, spodziewaj się, że coś się zepsuje; a dokładniej, spodziewaj się, że stan obserwatora zgaśnie, ponieważ nie otrzyma on aktualizacji z wcześniej obserwowanego obiektu. ” Każdy obserwator powinien zakończyć własną obserwację; niepowodzenie tego powinno być idealnie widoczne.
Peter Hosey,

3
Nic w pytaniu nie mówi o usuwaniu obserwatorów z innego kodu.
Glenn Maynard,

25

FWIW, [someObject observationInfo]wydaje się, że niljeśli someObjectnie ma żadnych obserwatorów. Nie ufałbym jednak temu zachowaniu, ponieważ nie widziałem tego udokumentowanego. Nie wiem też, jak czytać, observationInfoaby uzyskać konkretnych obserwatorów.


Czy wiesz, jak mogę odzyskać konkretnego obserwatora? objectAtIndex:nie daje pożądanego rezultatu.)
Eimantas

1
@MattDiPasquale Czy wiesz, jak odczytać w kodzie informacje o obserwacjach? Na wydrukach wychodzi dobrze, ale jest to wskazówka do pustki. Jak mam to przeczytać?
neeraj

observationInfo jest metodą debugowania udokumentowaną w dokumencie debugowania Xcode (coś z "magią" w tytule). Możesz spróbować to sprawdzić. Mogę powiedzieć, że jeśli chcesz wiedzieć, czy ktoś obserwuje Twój obiekt - robisz coś źle. Przemyśl swoją architekturę i logikę. Nauczyłem się tego na
własnej skórze

Źródło:NSKeyValueObserving.h
nefarianblack,

plus 1 za komiczny ślepy zaułek, ale wciąż nieco pomocną odpowiedź
Will Von Ullrich

4

Jedynym sposobem na to jest ustawienie flagi podczas dodawania obserwatora.


3
Gdy skończysz z BOOLami wszędzie, lepiej nadal utwórz obiekt opakowujący KVO, który obsługuje dodawanie obserwatora i usuwanie go. Może zapewnić, że obserwator zostanie usunięty tylko raz. Użyliśmy właśnie takiego obiektu i to działa.
bandejapaisa

świetny pomysł, jeśli nie zawsze obserwujesz.
Andre Simon

4

Kiedy dodajesz obserwatora do obiektu, możesz dodać go do NSMutableArraytakiego:

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

Jeśli chcesz anulować obserwację obiektów, możesz zrobić coś takiego:

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

Pamiętaj, że jeśli nie obserwujesz pojedynczego obiektu, usuń go z _observedObjectstablicy:

- (void)removeObservedObject:(id)object {
    if ([_observedObjects containsObject:object]) {
        [_observedObjects removeObject:object];
    }
}

1
Jeśli tak się dzieje w wielowątkowym świecie, musisz upewnić się, że twoja tablica jest ThreadSafe
shrutim

Utrzymujesz silne odwołanie do obiektu, które zwiększyłoby liczbę zachowań za każdym razem, gdy obiekt zostanie dodany do listy i nie zostanie zwolniony, dopóki jego odwołanie nie zostanie usunięte z tablicy. Wolałbym używać NSHashTable/, NSMapTableaby zachować słabe odniesienia.
atulkhatri

3

Moim zdaniem - działa to podobnie jak mechanizm retainCount. Nie możesz być pewien, że w tej chwili masz swojego obserwatora. Nawet jeśli sprawdzisz: self.observationInfo - nie można wiedzieć na pewno, że trzeba będzie / nie będzie miała obserwatorów w przyszłości.

Podobnie jak retainCount . Może metoda observationInfo nie jest aż tak bezużyteczna, ale używam jej tylko do debugowania.

W rezultacie - po prostu trzeba to robić jak w zarządzaniu pamięcią. Jeśli dodałeś obserwatora - po prostu usuń go, gdy go nie potrzebujesz. Podobnie jak przy użyciu metod viewWillAppear / viewWillDisappear itp. Na przykład:

-(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:@""];
}

Potrzebujesz pewnych specyficznych kontroli - zaimplementuj własną klasę, która obsługuje tablicę obserwatorów i używaj jej do kontroli.


[self removeObserver:nil forKeyPath:@""]; musi iść przed: [super viewWillDisappear:animated];
Joshua Hart

@JoshuaHart dlaczego?
quarezz

Ponieważ jest to metoda zrywania (dealloc). Kiedy zastępujesz jakąś metodę porzucenia, nazywasz super last. Na przykład: - (void) setupSomething { [super setupSomething]; … } - (void) tearDownSomething { … [super tearDownSomething]; }
Joshua Hart

viewWillDisapear nie jest metodą niszczenia i nie ma związku z dealloc. Jeśli przesuniesz się do przodu do stosu nawigacji, zostanie wywołany viewWillDisapear , ale widok pozostanie w pamięci. Widzę, do czego zmierzasz z logiką konfiguracji / porzucenia, ale zrobienie tego tutaj nie przyniesie żadnych rzeczywistych korzyści. Chciałbyś umieścić usuwanie przed super tylko wtedy, gdy masz jakąś logikę w klasie bazowej, która może kolidować z obecnym obserwatorem.
quarezz

3

[someObject observationInfo]powrócić, niljeśli nie ma obserwatora.

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

}

Zgodnie z Apple docs: observationInfo zwraca wskaźnik, który identyfikuje informacje o wszystkich obserwatorach zarejestrowanych w odbiorniku.
FredericK

Lepiej to zostało powiedziane w odpowiedzi @ mattdipasquale
Ben Leggiero

2

Cały sens wzorca obserwatora polega na tym, aby pozwolić obserwowanej klasie na „zapieczętowanie” - nie wiedząc lub nie dbając o to, czy jest obserwowana. Próbujesz wyraźnie złamać ten wzorzec.

Czemu?

Problem, który masz, polega na tym, że zakładasz, że jesteś obserwowany, a tak nie jest. Ten obiekt nie rozpoczął obserwacji. Jeśli chcesz, aby Twoja klasa miała kontrolę nad tym procesem, powinieneś rozważyć użycie centrum powiadomień. W ten sposób Twoja klasa ma pełną kontrolę nad tym, kiedy można obserwować dane. Dlatego nie obchodzi go, kto patrzy.


10
Pyta, jak słuchacz może dowiedzieć się, czy czegoś słucha, a nie w jaki sposób obserwowany obiekt może dowiedzieć się, czy jest obserwowany.
Glenn Maynard,

1

Nie jestem fanem tego rozwiązania typu try catch, więc przez większość czasu tworzę metodę subskrybowania i anulowania subskrypcji dla określonego powiadomienia w tej klasie. Na przykład te dwie metody subskrybują lub anulują subskrypcję obiektu do globalnego powiadomienia z klawiatury:

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

Wewnątrz tych metod używam własności prywatnej, która jest ustawiona na true lub false w zależności od stanu subskrypcji, jak na przykład:

@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

0

Oprócz odpowiedzi Adama chciałbym zasugerować użycie takiego makra

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

przykład użycia

- (void)dealloc {
    SafeRemoveObserver(someObject, self, somePath);
}

1
Jak szalone jest to, że rzuca wyjątek? Dlaczego po prostu nic nie robi, jeśli nic nie jest przyczepione?
Aran Mulholland
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.