Come posso sapere se a un oggetto è associato un osservatore di valori-chiave


142

se si dice a un oggetto c obiettivo di rimuovereObservers: per un percorso chiave e quel percorso chiave non è stato registrato, si spezza la tristezza. piace -

"Impossibile rimuovere un osservatore per il percorso chiave" theKeyPath "perché non è registrato come osservatore."

c'è un modo per determinare se un oggetto ha un osservatore registrato, quindi posso farlo

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

Sono entrato in questo scenario aggiornando una vecchia app su iOS 8 in cui un controller di visualizzazione veniva deallocato e generando l'eccezione "Impossibile rimuovere". Ho pensato che chiamando addObserver:in viewWillAppear:e corrispondentemente removeObserver:in viewWillDisappear:, le chiamate sono state correttamente accoppiato. Devo fare una soluzione rapida, quindi ho intenzione di implementare la soluzione try-catch e lasciare un commento per indagare ulteriormente sulla causa.
bneely

Ho a che fare con qualcosa di simile e vedo che ho bisogno di approfondire il mio design e di adattarlo in modo da non dover rimuovere di nuovo l'osservatore.
Bogdan,

usare un valore booleaco come suggerito in questa risposta ha funzionato al meglio per me: stackoverflow.com/a/37641685/4833705
Lance Samaria,

Risposte:


315

Prova a aggirare la tua chiamata removeObserver

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

12
1+ Buona risposta, ha funzionato per me e sono d'accordo con il tuo rant prima che fosse modificato.
Robert,

25
votato per rant cancellato che molto probabilmente sarei d'accordo.
Ben Gotow, il

12
Non c'è un'altra soluzione elegante qui? questo richiede almeno 2ms per utilizzo ... immaginatelo in una tabella
João Nunes,

19
Sottovalutato perché stai omettendo di dire che ciò non è sicuro per il codice di produzione e che probabilmente fallirà in qualsiasi momento. Aumentare le eccezioni tramite il codice quadro non è un'opzione in Cocoa.
Nikolai Ruhe,

6
Come utilizzare questo codice in swift 2.1. fai {provare self.playerItem? .removeObserver (self, forKeyPath: "status")} catturare l'errore come NSError {print (error.localizedDescription)} ottenere un avviso.
Vipulk617,

37

La vera domanda è perché non sai se la stai osservando o no.

Se lo stai facendo nella classe dell'oggetto osservato, fermati. Qualunque cosa stia osservando, si aspetta che continui ad osservarlo. Se tagli le notifiche dell'osservatore a sua insaputa, ti aspetti che le cose si rompano; più specificamente, aspettatevi che lo stato dell'osservatore diventi stantio poiché non riceve aggiornamenti dall'oggetto precedentemente osservato.

Se lo stai facendo nella classe dell'oggetto osservatore, ricorda semplicemente quali oggetti stai osservando (o, se osservi solo un oggetto, se lo stai osservando). Ciò presuppone che l'osservazione sia dinamica e tra due oggetti altrimenti non correlati; se l'osservatore possiede l'osservato, basta aggiungere l'osservatore dopo aver creato o conservare l'osservato e rimuovere l'osservatore prima di rilasciare l'osservato.

L'aggiunta e la rimozione di un oggetto come osservatore dovrebbe generalmente avvenire nella classe dell'osservatore e mai in quella osservata.


14
Caso d'uso: si desidera rimuovere gli osservatori in viewDidUnload e anche in dealloc. Questo li rimuove due volte e genererà l'eccezione se viewController viene scaricato da un avviso di memoria e quindi rilasciato. Come suggerisci di gestire questo scenario?
bandejapaisa,

2
@bandejapaisa: Praticamente quello che ho detto nella mia risposta: tieni traccia del fatto che sto osservando e prova a smettere di osservare solo se lo sono.
Peter Hosey,

41
No, questa non è una domanda interessante. Non dovresti tenerne traccia; dovresti essere in grado di annullare semplicemente la registrazione di tutti gli ascoltatori in dealloc, senza preoccuparti se ti è capitato di colpire il percorso del codice in cui è stato aggiunto o meno. Dovrebbe funzionare come removeObserver di NSNotificationCenter, a cui non importa se ne hai effettivamente uno o meno. Questa eccezione è semplicemente la creazione di bug laddove non esisterebbe altrimenti, il che è una cattiva progettazione dell'API.
Glenn Maynard,

1
@GlennMaynard: Come ho detto nella risposta, “Se tagli le notifiche dell'osservatore a sua insaputa, ti aspetti che le cose si rompano; più specificamente, aspettati che lo stato dell'osservatore diventi stantio poiché non riceve aggiornamenti dall'oggetto precedentemente osservato. " Ogni osservatore dovrebbe porre fine alla propria osservazione; in caso contrario, idealmente dovrebbe essere altamente visibile.
Peter Hosey,

3
Nulla nella domanda parla della rimozione di osservatori di altri codici.
Glenn Maynard,

25

FWIW, [someObject observationInfo]sembra essere nilse someObjectnon ha osservatori. Non mi fiderei di questo comportamento, tuttavia, poiché non l'ho visto documentato. Inoltre, non so leggere observationInfoper ottenere osservatori specifici.


Sai per caso come posso recuperare un osservatore specifico? objectAtIndex:non produce il risultato desiderato.)
Eimantas

1
@MattDiPasquale Sai come leggere l'osservazioneInfo nel codice? Nelle stampe sta uscendo bene, ma è un puntatore al vuoto. Come dovrei leggerlo?
neeraj,

observationInfo è un metodo di debug documentato nel documento di debug di Xcode (qualcosa con "magia" nel titolo). Puoi provare a cercarlo. Posso dire che se hai bisogno di sapere se qualcuno sta osservando il tuo oggetto, stai facendo qualcosa di sbagliato. Ripensa alla tua architettura e logica. L'ho imparato nel modo più duro.)
Eimantas,

Fonte:NSKeyValueObserving.h
nefarianblack,

più 1 per un comico vicolo cieco ma una risposta ancora piuttosto utile
Will Von Ullrich

4

L'unico modo per farlo è impostare un flag quando aggiungi un osservatore.


3
Finisci con i BOOL ovunque, è meglio creare comunque un oggetto wrapper KVO che gestisca l'aggiunta dell'osservatore e la sua rimozione. Può garantire che il tuo osservatore venga rimosso solo una volta. Abbiamo usato un oggetto proprio come questo e funziona.
bandejapaisa,

ottima idea se non stai sempre osservando.
Andre Simon,

4

Quando aggiungi un osservatore a un oggetto, puoi aggiungerlo a un NSMutableArraysimile:

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

Se vuoi non rispettare gli oggetti, puoi fare qualcosa del tipo:

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

Ricorda, se non osservi un singolo oggetto rimuovilo _observedObjectsdall'array:

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

1
Se ciò accade in un mondo multi-thread, è necessario assicurarsi che l'array sia ThreadSafe
shrutim

Stai mantenendo un riferimento forte di un oggetto, che aumenterebbe il conteggio delle modifiche ogni volta che un oggetto viene aggiunto nell'elenco e non verrà deallocato a meno che il suo riferimento non venga rimosso dall'array. Preferirei usare NSHashTable/ NSMapTableper mantenere i riferimenti deboli.
Atulkhatri,

3

Secondo me, funziona in modo simile al meccanismo retainCount. Non puoi essere sicuro che al momento hai il tuo osservatore. Anche se controlli: self.observationInfo - non puoi sapere con certezza che avrai / non avrai osservatori in futuro.

Come retainCount . Forse l' osservazioneInfo metodo non è esattamente quel tipo di inutile, ma lo uso solo a scopi di debug.

Di conseguenza, devi solo farlo come nella gestione della memoria. Se hai aggiunto un osservatore, rimuovilo quando non ti serve. Come usare i metodi viewWillAppear / viewWillDisappear ecc. Per esempio:

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

E hai bisogno di alcuni controlli specifici: implementa la tua classe che gestisce un array di osservatori e lo usa per i tuoi controlli.


[self removeObserver:nil forKeyPath:@""]; deve andare prima: [super viewWillDisappear:animated];
Joshua Hart,

@JoshuaHart perché?
Quarezz,

Perché è un metodo di demolizione (dealloc). Quando si esegue l'override di una sorta di metodo di smontaggio, si chiama super last. Come: - (void) setupSomething { [super setupSomething]; … } - (void) tearDownSomething { … [super tearDownSomething]; }
Joshua Hart,

viewWillDisapear non è un metodo di demolizione e non ha alcuna connessione con dealloc. Se si spinge in avanti verso lo stack di navigazione, verrà chiamato viewWillDisapear , ma la vista rimarrà in memoria. Vedo dove stai andando con la logica di installazione / smontaggio, ma farlo qui non darà alcun vantaggio reale. Dovresti mettere la rimozione prima di super solo se hai qualche logica nella classe base, che potrebbe entrare in conflitto con l'osservatore attuale.
Quarezz,

3

[someObject observationInfo]ritorna nilse non c'è osservatore.

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

}

Secondo i documenti di Apple: observationInfo restituisce un puntatore che identifica le informazioni su tutti gli osservatori registrati con il ricevitore.
FredericK

Questo è stato detto meglio nella risposta di @ mattdipasquale
Ben Leggiero

2

Il punto centrale del modello di osservatore è quello di consentire a una classe osservata di essere "sigillata" - di non sapere o preoccuparsi se viene osservata. Stai esplicitamente cercando di rompere questo schema.

Perché?

Il problema che stai riscontrando è che stai assumendo di essere osservato quando non lo sei. Questo oggetto non ha avviato l'osservazione. Se vuoi che la tua classe abbia il controllo di questo processo, allora dovresti prendere in considerazione l'uso del centro di notifica. In questo modo la tua classe ha il pieno controllo su quando è possibile osservare i dati. Quindi, non importa chi sta guardando.


10
Sta chiedendo come l' ascoltatore può scoprire se sta ascoltando qualcosa, non come l'oggetto osservato può scoprire se viene osservato.
Glenn Maynard,

1

Non sono un fan di quella soluzione di prova, quindi quello che faccio la maggior parte delle volte è che creo un metodo di iscrizione e annullamento dell'iscrizione per una specifica notifica all'interno di quella classe. Ad esempio, questi due metodi sottoscrivono o annullano la sottoscrizione dell'oggetto alla notifica della tastiera globale:

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

All'interno di questi metodi utilizzo una proprietà privata impostata su true o false a seconda dello stato dell'abbonamento in questo modo:

@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

Oltre alla risposta di Adam, vorrei suggerire di utilizzare macro come questa

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

esempio di utilizzo

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

1
Quanto è folle che genera un'eccezione? Perché non fa proprio niente se nulla è attaccato?
Aran Mulholland,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.