Objective-C: dove rimuovere l'osservatore per NSNotification?


102

Ho una classe C obiettivo. In esso, ho creato un metodo init e ho impostato una NSNotification in esso

//Set up NSNotification
[[NSNotificationCenter defaultCenter] addObserver:self 
                                         selector:@selector(getData)
                                             name:@"Answer Submitted"
                                           object:nil];

Dove imposto il [[NSNotificationCenter defaultCenter] removeObserver:self]in questa classe? So che per a UIViewController, posso aggiungerlo al viewDidUnloadmetodo Quindi cosa devo fare se ho appena creato una Classe c obiettivo?


L'ho inserito nel metodo dealloc.
onnoweb

1
Il metodo dealloc non è stato creato automaticamente per me quando ho creato la classe c dell'obiettivo, quindi va bene per me aggiungerlo?
Zhen

Sì, puoi implementarlo -(void)dealloce quindi aggiungerlo removeObserser:self. Questo è il modo più consigliato per mettereremoveObservers:self
petershine

Va ancora bene inserire il deallocmetodo in iOS 6?
wcochran

2
Sì, va bene usare dealloc nei progetti ARC a patto che non chiami [super dealloc] (riceverai un errore del compilatore se chiami [super dealloc]). E sì, puoi sicuramente mettere il tuo removeObserver in dealloc.
Phil

Risposte:


112

La risposta generica sarebbe "non appena non avrai più bisogno delle notifiche". Questa ovviamente non è una risposta soddisfacente.

Ti consiglio di aggiungere una chiamata [notificationCenter removeObserver: self]al metodo deallocdi quelle classi, che intendi utilizzare come osservatori, poiché è l'ultima possibilità per annullare la registrazione di un osservatore in modo pulito. Ciò, tuttavia, ti proteggerà solo dagli arresti anomali dovuti al centro notifiche che notifica gli oggetti morti. Non può proteggere il tuo codice dalla ricezione di notifiche, quando i tuoi oggetti non sono ancora / non più in uno stato in cui possono gestire correttamente la notifica. Per questo ... Vedi sopra.

Modifica (poiché la risposta sembra attirare più commenti di quanto avrei pensato) Tutto quello che sto cercando di dire qui è: è davvero difficile dare consigli generali su quando è meglio rimuovere l'osservatore dal centro notifiche, perché dipende:

  • Nel tuo caso d'uso (quali notifiche vengono osservate? Quando vengono inviate?)
  • L'implementazione dell'osservatore (Quando è pronto a ricevere notifiche? Quando non è più pronto?)
  • Il tempo di vita previsto dell'osservatore (è legato a qualche altro oggetto, ad esempio, una vista o un controller della vista?)
  • ...

Quindi, il miglior consiglio generale che posso fornire: per proteggere la tua app. contro almeno un possibile fallimento, removeObserver:balla dentro dealloc, poiché è l'ultimo punto (nella vita dell'oggetto), dove puoi farlo in modo pulito. Ciò che questo non significa è: "rimanda la rimozione finché non deallocviene chiamato e tutto andrà bene". Invece, rimuovere l'osservatore non appena l'oggetto non è più pronto (o richiesto) per ricevere le notifiche . Questo è il momento giusto esatto. Sfortunatamente, non conoscendo le risposte a nessuna delle domande sopra menzionate, non riesco nemmeno a immaginare quando sarebbe quel momento.

Puoi sempre tranquillamente removeObserver:un oggetto più volte (e tutte tranne la prima chiamata con un dato osservatore saranno nops). Quindi: pensa di farlo (di nuovo) deallocsolo per essere sicuro, ma prima di tutto: fallo al momento appropriato (che è determinato dal tuo caso d'uso).


4
Questo non è sicuro con ARC e potrebbe potenzialmente causare una perdita. Vedi questa discussione: cocoabuilder.com/archive/cocoa/311831-arc-and-dealloc.html
MobileMon

3
@MobileMon L'articolo a cui hai collegato sembra chiarire il mio punto. Cosa mi sto perdendo?
Dirk

Suppongo che dovrebbe essere notato in quanto si dovrebbe rimuovere l'osservatore da qualche altra parte oltre a dealloc. Ad esempio, viewwilldisappear
MobileMon

1
@MobileMon - sì. Spero che questo sia il punto che sto comunicando con la mia risposta. La rimozione dell'osservatore in deallocè solo l'ultima linea di difesa contro l'arresto anomalo dell'app a causa di un successivo accesso a un oggetto decallocato. Ma il posto giusto per annullare la registrazione di un osservatore è di solito altrove (e spesso, molto prima nel ciclo di vita dell'oggetto). Non sto cercando di dire qui "Ehi, fallo dentro dealloce andrà tutto bene".
Dirk

@MobileMon "Ad esempio, viewWillDisappear" Il problema nel dare un consiglio concreto è che dipende davvero dal tipo di oggetto che registri come osservatore per quale tipo di evento. Essa può essere la soluzione giusta per annullare la registrazione di osservatore viewWillDisappear(o viewDidUnload) per UIViewControllers, ma che in realtà dipende dal caso d'uso.
Dirk

39

Nota: questo è stato testato e funziona al 100% percento

veloce

override func viewWillDisappear(animated: Bool){
    super.viewWillDisappear(animated)

    if self.navigationController!.viewControllers.contains(self) == false  //any other hierarchy compare if it contains self or not
    {
        // the view has been removed from the navigation stack or hierarchy, back is probably the cause
        // this will be slow with a large stack however.

        NSNotificationCenter.defaultCenter().removeObserver(self)
    }
}

PresentedViewController

override func viewWillDisappear(animated: Bool){
    super.viewWillDisappear(animated)

    if self.isBeingDismissed()  //presented view controller
    {
        // remove observer here
        NSNotificationCenter.defaultCenter().removeObserver(self)
    }
}

Objective-C

In iOS 6.0 > version, è meglio rimuovere l'osservatore in viewWillDisappearquanto il viewDidUnloadmetodo è deprecato.

 [[NSNotificationCenter defaultCenter] removeObserver:observerObjectHere];

Molte volte è meglio remove observerquando la vista è stata rimossa dal file navigation stack or hierarchy.

- (void)viewWillDisappear:(BOOL)animated{
 if (![[self.navigationController viewControllers] containsObject: self]) //any other hierarchy compare if it contains self or not
    {
        // the view has been removed from the navigation stack or hierarchy, back is probably the cause
        // this will be slow with a large stack however.

        [[NSNotificationCenter defaultCenter] removeObserver:observerObjectHere];
    }
}

PresentedViewController

- (void)viewWillDisappear:(BOOL)animated{
    if ([self isBeingDismissed] == YES) ///presented view controller
    {
        // remove observer here
        [[NSNotificationCenter defaultCenter] removeObserver:observerObjectHere];
    }
}

8
Ad eccezione del fatto che un controller potrebbe comunque volere notifiche quando la sua vista non è mostrata (ad esempio per ricaricare una tableView).
wcochran

2
@wcochran ricarica / aggiorna automaticamenteviewWillAppear:
Richard

@Prince puoi spiegare perché viewWillDisapper è meglio di dealloc? quindi dobbiamo aggiungere l'osservatore a sé, quindi quando il sé verrà eliminato dalla memoria chiamerà dealloc e quindi tutti gli osservatori verranno eliminati, non è una buona logica.
Matrosov Alexander

Chiamare removeObserver:selfuno qualsiasi degli UIViewControllereventi del ciclo di vita è quasi garantito per rovinare la tua settimana. Più lettura: subjective-objective-c.blogspot.com/2011/04/...
cbowns

1
Effettuare le removeObserverchiamate viewWillDisappearcome indicato è sicuramente la strada giusta se il controller viene presentato tramite pushViewController. Se li metti deallocinvece dealloc, non verranno mai chiamati - nella mia esperienza almeno ...
Christopher King

38

Da iOS 9 non è più necessario rimuovere gli osservatori.

In OS X 10.11 e iOS 9.0 NSNotificationCenter e NSDistributedNotificationCenter non invieranno più notifiche agli osservatori registrati che potrebbero essere deallocati.

https://developer.apple.com/library/mac/releasenotes/Foundation/RN-Foundation/index.html#10_11NotificationCenter


2
Forse non invieranno messaggi agli osservatori, ma credo che manterranno un forte riferimento a loro come ho capito. In tal caso tutti gli osservatori rimarranno in memoria e produrranno una perdita. Correggimi se sbaglio.
abete

6
La documentazione collegata entra nei dettagli a riguardo. TL; DR: è un riferimento debole.
Sebastian

ma ovviamente è ancora necessario nel caso in cui mantieni l'oggetto che fa riferimento a loro e non vuoi più ascoltare le notifiche
TheEye

25

Se l'osservatore viene aggiunto a un controller di visualizzazione , consiglio vivamente di aggiungerlo viewWillAppeare rimuoverlo viewWillDisappear.


Sono curioso, @RickiG: perché consigli di usare viewWillAppeare viewWillDisappearper i viewControllers?
Isaac Overacker

2
@IsaacOveracker per alcuni motivi: il codice di configurazione (ad es. LoadView e viewDidLoad) potrebbe potenzialmente causare l'attivazione delle notifiche e il controller deve riflettere questo prima che venga visualizzato. Se lo fai in questo modo, ci sono alcuni vantaggi. Al momento hai deciso di "lasciare" il controller non ti interessano le notifiche e non ti faranno fare logica mentre il controller viene spinto fuori dallo schermo ecc. Ci sono casi speciali in cui il controller dovrebbe ricevere notifiche quando lo è fuori dallo schermo immagino che tu non possa farlo. Ma eventi del genere dovrebbero probabilmente essere nel tuo modello.
RickiG

1
@IsaacOveracker anche con ARC sarebbe strano implementare dealloc per annullare l'iscrizione alle notifiche.
RickiG

4
Di quelli che ho provato, con iOS7 questo è il modo migliore per registrare / rimuovere osservatori quando si lavora con UIViewControllers. L'unico problema è che, in molti casi, non vuoi che l'osservatore venga rimosso quando usi UINavigationController e spingi un altro UIViewController nello stack. Soluzione: è possibile verificare se il VC viene visualizzato in viewWillDisappear chiamando [self isBeingDismissed].
lekksi

Lo schiocco del controller della vista dal controller di navigazione potrebbe non causare la deallocchiamata immediata. Tornare al controller di visualizzazione può quindi causare più notifiche se l'osservatore viene aggiunto nei comandi di inizializzazione.
Jonathan Lin

20
-(void) dealloc {
      [[NSNotificationCenter defaultCenter] removeObserver:self];
      [super dealloc];
}

4
Capovolgerei l'ordine di queste istruzioni ... Usare selfdopo [super dealloc]mi rende nervoso ... (anche se è improbabile che il ricevitore dereferenzi effettivamente il puntatore in alcun modo, beh, non si sa mai, come hanno implementato NSNotificationCenter)
Dirk

Hm. ha funzionato per me. Hai notato comportamenti insoliti?
Legolas

1
Dirk ha ragione - questo non è corretto. [super dealloc]deve sempre essere l' ultima affermazione del tuo deallocmetodo. Distrugge il tuo oggetto; dopo che viene eseguito, non hai più un valido self. / cc @Dirk
jscs

38
Se si utilizza ARC su iOS 5+, penso che [super dealloc]non sia più necessario
pixelfreak

3
@pixelfreak più forte, non è consentito sotto ARC chiamare [super dealloc]
tapmonkey


7

In rapido utilizzo deinit perché dealloc non è disponibile:

deinit {
    ...
}

Documentazione rapida:

Un deinitializer viene chiamato immediatamente prima della deallocazione di un'istanza di classe. Si scrivono deinitializer con la parola chiave deinit, in modo simile a come vengono scritti gli intializer con la parola chiave init. I deinizializzatori sono disponibili solo sui tipi di classe.

In genere non è necessario eseguire la pulizia manuale quando le istanze vengono deallocate. Tuttavia, quando si lavora con le proprie risorse, potrebbe essere necessario eseguire da soli alcune pulizie aggiuntive. Ad esempio, se crei una classe personalizzata per aprire un file e scrivere alcuni dati su di esso, potresti dover chiudere il file prima che l'istanza della classe venga deallocata.


5

* modifica: questo consiglio si applica a iOS <= 5 (anche lì dovresti aggiungere viewWillAppeare rimuovere viewWillDisappear- tuttavia il consiglio si applica se per qualche motivo hai aggiunto l'osservatore viewDidLoad)

Se hai aggiunto l'osservatore viewDidLoad, dovresti rimuoverlo in entrambi dealloce viewDidUnload. Altrimenti finirai per aggiungerlo due volte quando viewDidLoadviene chiamato after viewDidUnload(questo accadrà dopo un avviso di memoria). Questo non è necessario in iOS 6 dove viewDidUnloadè deprecato e non verrà chiamato (perché le visualizzazioni non vengono più scaricate automaticamente).


2
Benvenuto in StackOverflow. Controlla le FAQ di MarkDown (icona del punto interrogativo accanto alla casella di modifica della domanda / risposta). L'utilizzo di Markdwon migliorerà l'usabilità della tua risposta.
marko

5

A mio parere, il seguente codice non ha senso in ARC :

- (void)dealloc
{
      [[NSNotificationCenter defaultCenter] removeObserver:self];
      [super dealloc];
}

In iOS 6 , inoltre, non ha senso rimuovere gli osservatori viewDidUnload, perché ora è stato deprecato.

Per riassumere, lo faccio sempre viewDidDisappear. Tuttavia, dipende anche dalle tue esigenze, proprio come ha detto @Dirk.


Molte persone stanno ancora scrivendo codice per versioni precedenti di iOS rispetto a iOS6 .... :-)
lnafziger

In ARC puoi usare questo codice ma senza la riga [super dealloc]; Puoi vedere di più qui: developer.apple.com/library/ios/#releasenotes/ObjectiveC/…
Alex

1
E se un normale NSObject fosse l'osservatore di una notifica? Useresti dealloc in questo caso?
qix

4

Penso di aver trovato una risposta affidabile ! Ho dovuto, poiché le risposte di cui sopra sono ambigue e sembrano contraddittorie. Ho esaminato i libri di cucina e le guide di programmazione.

Innanzitutto, lo stile di addObserver:in viewWillAppear:e removeObserver:in viewWillDisappear:non funziona per me (l'ho testato) perché sto postando una notifica in un controller di visualizzazione figlio per eseguire codice nel controller di visualizzazione padre. Userei questo stile solo se stavo postando e ascoltando la notifica nello stesso controller di visualizzazione.

La risposta su cui mi affiderò di più, l'ho trovata nella Programmazione iOS: Big Nerd Ranch Guide 4th. Mi fido dei ragazzi di BNR perché hanno centri di formazione iOS e non stanno solo scrivendo un altro libro di cucina. Probabilmente è nel loro interesse essere precisi.

BNR esempio uno: addObserver:in init:, removeObserver:indealloc:

BNR esempio due: addObserver:in awakeFromNib:, removeObserver:indealloc:

... quando rimuovono l'osservatore dealloc:non usano[super dealloc];

Spero che questo aiuti la prossima persona ...

Sto aggiornando questo post perché Apple ora ha quasi completamente abbandonato gli Storyboard, quindi quanto sopra potrebbe non essere applicabile a tutte le situazioni. La cosa importante (e il motivo per cui ho aggiunto questo post in primo luogo) è prestare attenzione se viewWillDisappear:vieni chiamato. Non era per me quando l'applicazione è entrata in background.


È difficile dire se questo sia giusto poiché il contesto è importante. È già menzionato alcune volte, ma dealloc ha poco senso in un contesto ARC (che è l'unico contesto ormai). Inoltre, non è prevedibile quando viene chiamato dealloc: viewWillDisappear è più facile da controllare. Una nota a margine: se tuo figlio ha bisogno di comunicare qualcosa al suo genitore, il pattern delegato suona come una scelta migliore.
RickiG

2

La risposta accettata non è sicura e potrebbe causare una perdita di memoria. Si prega di lasciare l'annullamento della registrazione in dealloc ma anche l'annullamento della registrazione in viewWillDisappear (questo è ovviamente se ti registri in viewWillAppear) .... ECCO COSA HO FATTO IN OGNI MODO E FUNZIONA GRANDE! :)


1
Sono d'accordo con questa risposta. Se non rimuovo gli osservatori in viewWillDisappear, riscontro avvisi di memoria e perdite che portano a arresti anomali dopo un uso intensivo dell'app.
SarpErdag

2

È importante notare anche che viewWillDisappearviene chiamato anche quando il controller di visualizzazione presenta una nuova UIView. Questo delegato indica semplicemente che la vista principale del controller di visualizzazione non è visibile sul display.

In questo caso, la deallocazione della notifica in viewWillDisappearpotrebbe essere scomoda se utilizziamo la notifica per consentire a UIview di comunicare con il controller della visualizzazione padre.

Come soluzione di solito rimuovo l'osservatore in uno di questi due metodi:

- (void)viewWillDisappear:(BOOL)animated {
    NSLog(@"viewController will disappear");
    if ([self isBeingDismissed]) {
        NSLog(@"viewController is being dismissed");
        [[NSNotificationCenter defaultCenter] removeObserver:self name:@"actionCompleted" object:nil];
    }
}

-(void)dealloc {
    NSLog(@"viewController is being deallocated");
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"actionCompleted" object:nil];
}

Per motivi simili, quando emetto la notifica per la prima volta, devo tenere conto del fatto che ogni volta che una vista con appare sopra il controller, il viewWillAppearmetodo viene attivato. Ciò a sua volta genererà più copie della stessa notifica. Poiché non c'è un modo per verificare se una notifica è già attiva, ovvio al problema rimuovendo la notifica prima di aggiungerla:

- (void)viewWillAppear:(BOOL)animated {
    NSLog(@"viewController will appear");
    // Add observers
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"imageGenerated" object:nil]; // This is added to avoid duplicate notifications when the view is presented again
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receivedImageFromCameraOrPhotolibraryMethodOnListener:) name:@"actionCompleted" object:nil];

}

-1

SWIFT 3

Esistono due casi di utilizzo delle notifiche: - sono necessarie solo quando il controller di visualizzazione è sullo schermo; - sono necessari sempre, anche se l'utente ha aperto un'altra schermata rispetto a quella corrente.

Per il primo caso le posizioni corrette per aggiungere e rimuovere l'osservatore sono:

/// Add observers
///
/// - Parameter animated: the animation flag
override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    NotificationCenter.default.addObserver(...)
}

/// Remove observers
///
/// - Parameter animated: the animation flag
override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    NotificationCenter.default.removeObserver(self)
}

per il secondo caso il modo corretto è:

/// Add observers
override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(...)
}

/// Remove observers
///
/// - Parameter animated: the animation flag
override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    if self.isBeingDismissed // remove only when view controller is removed disappear forever
    || !(self.navigationController?.viewControllers.contains(self) ?? true) {
        NotificationCenter.default.removeObserver(self)
    }
}

E mai messo removeObserverin deinit{ ... }- è un errore!


-1
override func viewDidLoad() {   //add observer
  super.viewDidLoad()
  NotificationCenter.default.addObserver(self, selector:#selector(Yourclassname.method), name: NSNotification.Name(rawValue: "NotificationIdentifier"), object: nil)
}

override func viewWillDisappear(_ animated: Bool) {    //remove observer
    super.viewWillDisappear(true)
    NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: "NotificationIdentifier"), object: nil)
}
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.