Ziel C: Wo kann der Beobachter für die NS-Benachrichtigung entfernt werden?

102

Ich habe eine objektive C-Klasse. Darin habe ich eine init-Methode erstellt und darin eine NSNotification eingerichtet

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

Wo stelle ich das [[NSNotificationCenter defaultCenter] removeObserver:self]in dieser Klasse ein? Ich weiß, dass ich es für a UIViewControllerin die viewDidUnloadMethode einfügen kann. Was muss also getan werden, wenn ich gerade eine objektive c-Klasse erstellt habe?

Zhen
quelle
Ich habe es in die Dealloc-Methode eingefügt.
Onnoweb
1
Die Dealloc-Methode wurde nicht automatisch für mich erstellt, als ich die Ziel-C-Klasse erstellt habe. Ist es also in Ordnung, sie hinzuzufügen?
Zhen
Ja, Sie können es implementieren -(void)deallocund dann hinzufügen removeObserser:self. Dies ist die am meisten empfohlene Methode, umremoveObservers:self
petershine
Ist es noch in Ordnung, die deallocMethode in iOS 6 einzufügen?
Wcochran
2
Ja, es ist in Ordnung, Dealloc in ARC-Projekten zu verwenden, solange Sie nicht [Super Dealloc] aufrufen (Sie erhalten einen Compilerfehler, wenn Sie [Super Dealloc] aufrufen). Und ja, Sie können Ihren removeObserver definitiv in den Dealloc stellen.
Phil

Antworten:

112

Die generische Antwort wäre "sobald Sie die Benachrichtigungen nicht mehr benötigen". Dies ist offensichtlich keine zufriedenstellende Antwort.

Ich würde empfehlen, dass Sie einen Anruf hinzufügen , [notificationCenter removeObserver: self]in Verfahren deallocdieser Klassen, die Sie als Beobachter verwenden möchten, da es die letzte Chance zu austragen Beobachter sauber ist. Dies schützt Sie jedoch nur vor Abstürzen, da das Benachrichtigungscenter tote Objekte benachrichtigt. Es kann Ihren Code nicht vor dem Empfang von Benachrichtigungen schützen, wenn sich Ihre Objekte noch nicht / nicht mehr in einem Zustand befinden, in dem sie die Benachrichtigung ordnungsgemäß verarbeiten können. Dafür ... Siehe oben.

Bearbeiten (da die Antwort mehr Kommentare zu zeichnen scheint, als ich gedacht hätte) Ich versuche hier nur zu sagen: Es ist wirklich schwierig, allgemeine Ratschläge zu geben, wann es am besten ist, den Beobachter aus dem Benachrichtigungscenter zu entfernen, da dies davon abhängt:

  • Zu Ihrem Anwendungsfall (Welche Benachrichtigungen werden beobachtet? Wann werden sie gesendet?)
  • Die Implementierung des Beobachters (Wann ist er bereit, Benachrichtigungen zu erhalten? Wann ist er nicht mehr bereit?)
  • Die beabsichtigte Lebensdauer des Beobachters (Ist sie an ein anderes Objekt gebunden, z. B. eine Ansicht oder eine Ansichtssteuerung?)
  • ...

Der beste allgemeine Rat, den ich finden kann: Zum Schutz Ihrer App. removeObserver:Tanzen Sie gegen mindestens einen möglichen Fehler, deallocda dies der letzte Punkt (im Leben des Objekts) ist, an dem Sie dies sauber tun können. Was dies nicht bedeutet ist: "Verschieben Sie die Entfernung dealloceinfach, bis sie aufgerufen wird, und alles wird in Ordnung sein". Entfernen Sie stattdessen den Beobachter , sobald das Objekt nicht mehr bereit (oder erforderlich) ist, Benachrichtigungen zu erhalten . Das ist genau der richtige Moment. Da ich die Antworten auf eine der oben genannten Fragen nicht kenne, kann ich leider nicht einmal erraten, wann dieser Moment sein würde.

Sie können removeObserver:ein Objekt immer mehrmals sicher sichern (und alle bis auf den ersten Anruf mit einem bestimmten Beobachter sind Nops). Also: Überlegen Sie dealloc, ob Sie es (erneut) tun möchten, um sicherzugehen, aber in erster Linie: Machen Sie es zum richtigen Zeitpunkt (der von Ihrem Anwendungsfall bestimmt wird).

Dolch
quelle
4
Dies ist bei ARC nicht sicher und kann möglicherweise ein Leck verursachen. Siehe diese Diskussion: cocoabuilder.com/archive/cocoa/311831-arc-and-dealloc.html
MobileMon
3
@MobileMon Der Artikel, auf den Sie verlinkt haben, scheint meinen Standpunkt zu verdeutlichen. Was vermisse ich ?
Dirk
Ich nehme an, es sollte angemerkt werden, dass man Beobachter an einem anderen Ort als Dealloc entfernen sollte. Zum Beispiel wird die Ansicht verschwinden
MobileMon
1
@ MobileMon - ja. Ich hoffe, dass das der Punkt ist, den ich mit meiner Antwort verstehe. Das Entfernen des Beobachters deallocist nur eine letzte Verteidigungslinie gegen das Abstürzen der App aufgrund eines späteren Zugriffs auf ein nicht zugeordnetes Objekt. Der richtige Ort, um die Registrierung eines Beobachters aufzuheben, ist normalerweise an einem anderen Ort (und oft viel früher im Lebenszyklus des Objekts). Ich versuche hier nicht zu sagen "Hey, mach es einfach deallocund alles wird gut".
Dirk
@MobileMon "Zum Beispiel viewWillDisappear" Das Problem bei der Abgabe eines konkreten Hinweises ist, dass es wirklich davon abhängt, welche Art von Objekt Sie als Beobachter für welche Art von Ereignis registrieren. Es mag die richtige Lösung sein, die Registrierung eines Beobachters in viewWillDisappear(oder viewDidUnload) für UIViewControllers aufzuheben, aber das hängt wirklich vom Anwendungsfall ab.
Dirk
38

Seit iOS 9 müssen keine Beobachter mehr entfernt werden.

In OS X 10.11 und iOS 9.0 senden NSNotificationCenter und NSDistributedNotificationCenter keine Benachrichtigungen mehr an registrierte Beobachter, die möglicherweise freigegeben werden.

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

Sebastian
quelle
2
Vielleicht werden sie keine Nachrichten an Beobachter senden, aber ich glaube, sie werden nach meinem Verständnis einen starken Bezug zu ihnen haben. In diesem Fall bleiben alle Beobachter im Gedächtnis und erzeugen ein Leck. С Korrigieren Sie mich, wenn ich falsch liege.
Tanne
6
Die verlinkte Dokumentation geht darauf detailliert ein. TL; DR: Es ist eine schwache Referenz.
Sebastian
Aber natürlich ist es immer noch notwendig, wenn Sie das Objekt, auf das sie verweisen,
beibehalten
38

Hinweis: Dies wurde getestet und funktioniert zu 100%

Schnell

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)
    }
}

Ziel c

In iOS 6.0 > versionist es besser, Beobachter zu entfernen, viewWillDisappearda die viewDidUnloadMethode veraltet ist.

 [[NSNotificationCenter defaultCenter] removeObserver:observerObjectHere];

Es ist um ein Vielfaches besser, remove observerwenn die Ansicht aus dem entfernt wurde 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];
    }
}
Paresh Navadiya
quelle
8
Außer ein Controller möchte möglicherweise immer noch Benachrichtigungen, wenn seine Ansicht nicht angezeigt wird (z. B. um eine tableView neu zu laden).
Wcochran
2
@wcochran automatisch neu laden / aktualisieren inviewWillAppear:
Richard
@ Prince kannst du erklären, warum viewWillDisapper besser ist als Dealloc? Wir haben also einen Beobachter zum Selbst hinzugefügt. Wenn das Selbst aus dem Speicher gelöscht wird, ruft es den Dealloc auf und dann werden alle Beobachter gelöscht. Ist dies keine gute Logik?
Matrosov Alexander
Das Aufrufen removeObserver:selfeines der UIViewControllerLebenszyklusereignisse ruiniert fast garantiert Ihre Woche. Weitere
Informationen
1
Das Angeben der removeObserverAnrufe viewWillDisappearwie angegeben ist definitiv der richtige Weg, wenn der Controller über präsentiert wird pushViewController. Wenn Sie sie deallocstattdessen eingeben, deallocwird sie - zumindest meiner Erfahrung nach
Christopher King
25

Wenn der Beobachter einem Ansichts-Controller hinzugefügt wird , empfehle ich dringend, ihn hinzuzufügen viewWillAppearund zu entfernen viewWillDisappear.

RickiG
quelle
Ich bin neugierig, @RickiG: Warum empfehlen Sie die Verwendung von viewWillAppearund viewWillDisappearfür viewController?
Isaac Overacker
2
@IsaacOveracker einige Gründe: Ihr Setup-Code (z. B. loadView und viewDidLoad) kann möglicherweise dazu führen, dass die Benachrichtigungen ausgelöst werden, und Ihr Controller muss dies berücksichtigen, bevor er angezeigt wird. Wenn Sie es so machen, gibt es ein paar Vorteile. In dem Moment, in dem Sie beschlossen haben, den Controller zu "verlassen", interessieren Sie sich nicht für die Benachrichtigungen und sie veranlassen Sie nicht, Logik zu betreiben, während der Controller vom Bildschirm usw. geschoben wird. Es gibt spezielle Fälle, in denen der Controller Benachrichtigungen erhalten sollte, wenn dies der Fall ist Off-Screen Ich denke, Sie können das nicht tun. Aber solche Ereignisse sollten wahrscheinlich in Ihrem Modell enthalten sein.
RickiG
1
@IsaacOveracker auch mit ARC wäre es seltsam, Dealloc zu implementieren, um Benachrichtigungen abzubestellen.
RickiG
4
Von denen, die ich versucht habe, ist dies mit iOS7 der beste Weg, Beobachter zu registrieren / zu entfernen, wenn Sie mit UIViewControllern arbeiten. Der einzige Haken ist, dass Sie in vielen Fällen nicht möchten, dass der Beobachter entfernt wird, wenn Sie UINavigationController verwenden und einen anderen UIViewController auf den Stapel schieben. Lösung: Sie können überprüfen, ob der VC in viewWillDisappear angezeigt wird, indem Sie [self isBeingDismissed] aufrufen.
Lekksi
Wenn Sie den View Controller vom Navigationscontroller entfernen, wird er möglicherweise nicht deallocsofort aufgerufen. Das Zurückkehren in den Ansichts-Controller kann dann zu mehreren Benachrichtigungen führen, wenn Beobachter in Initialisierungsbefehlen hinzugefügt wird.
Jonathan Lin
20
-(void) dealloc {
      [[NSNotificationCenter defaultCenter] removeObserver:self];
      [super dealloc];
}
Legolas
quelle
4
Ich würde die Reihenfolge dieser Anweisungen umkehren ... Die Verwendung von selfafter [super dealloc]macht mich nervös ... (selbst wenn es unwahrscheinlich ist, dass der Empfänger den Zeiger in irgendeiner Weise dereferenziert, wissen Sie nie, wie sie implementiert wurden NSNotificationCenter)
Dirk
Hm. es hat bei mir funktioniert. Haben Sie ungewöhnliches Verhalten bemerkt?
Legolas
1
Dirk hat recht - das ist falsch. [super dealloc]muss immer die letzte Aussage Ihrer deallocMethode sein. Es zerstört dein Objekt; Nachdem es ausgeführt wird, haben Sie keine gültige selfmehr. / cc @Dirk
jscs
38
Wenn Sie ARC unter iOS 5+ verwenden, denke ich [super dealloc] nach nicht mehr benötigt
pixelfreak
3
@pixelfreak stärker, es ist unter ARC nicht erlaubt, [super dealloc]
tapmonkey aufzurufen
8

Im Allgemeinen habe ich es in die deallocMethode eingefügt.

Raphael Petegrosso
quelle
7

Verwenden Sie schnell deinit, da kein Dealloc verfügbar ist:

deinit {
    ...
}

Schnelle Dokumentation:

Ein Deinitializer wird unmittelbar vor der Freigabe einer Klasseninstanz aufgerufen. Sie schreiben Deinitializer mit dem Schlüsselwort deinit, ähnlich wie Intializer mit dem Schlüsselwort init geschrieben werden. Deinitializer sind nur für Klassentypen verfügbar.

Normalerweise müssen Sie keine manuelle Bereinigung durchführen, wenn Ihre Instanzen freigegeben werden. Wenn Sie jedoch mit Ihren eigenen Ressourcen arbeiten, müssen Sie möglicherweise selbst eine zusätzliche Bereinigung durchführen. Wenn Sie beispielsweise eine benutzerdefinierte Klasse erstellen, um eine Datei zu öffnen und Daten in diese zu schreiben, müssen Sie die Datei möglicherweise schließen, bevor die Zuordnung der Klasseninstanz aufgehoben wird.

Morten Holmgaard
quelle
5

* Bearbeiten: Dieser Hinweis gilt für iOS <= 5 (auch dort sollten Sie hinzufügen viewWillAppearund entfernen viewWillDisappear- der Hinweis gilt jedoch, wenn Sie aus irgendeinem Grund den Beobachter hinzugefügt haben viewDidLoad).

Wenn Sie den Beobachter hinzugefügt haben viewDidLoad, sollten Sie ihn in beiden deallocund entfernen viewDidUnload. Andernfalls fügen Sie es zweimal hinzu, wenn viewDidLoades danach aufgerufen wird viewDidUnload(dies geschieht nach einer Speicherwarnung). Dies ist in iOS 6 nicht erforderlich, wo viewDidUnloades veraltet ist und nicht aufgerufen wird (da Ansichten nicht mehr automatisch entladen werden).

Ehren
quelle
2
Willkommen bei StackOverflow. Bitte lesen Sie die MarkDown-FAQ (Fragezeichen neben dem Frage- / Antwort-Bearbeitungsfeld). Die Verwendung von Markdwon verbessert die Benutzerfreundlichkeit Ihrer Antwort.
Marko
5

Meiner Meinung nach macht der folgende Code in ARC keinen Sinn :

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

In iOS 6 macht es auch keinen Sinn, Beobachter zu entfernen viewDidUnload, da dies jetzt veraltet ist.

Zusammenfassend mache ich es immer in viewDidDisappear. Es hängt jedoch auch von Ihren Anforderungen ab, genau wie @Dirk sagte.

Kimimaro
quelle
Viele Leute schreiben immer noch Code für ältere Versionen von iOS als iOS6 .... :-)
lnafziger
In ARC können Sie diesen Code verwenden, jedoch ohne die Zeile [super dealloc]; Sie können mehr hier sehen: developer.apple.com/library/ios/#releasenotes/ObjectiveC/…
Alex
1
Was wäre, wenn Sie ein reguläres NSObject als Beobachter einer Benachrichtigung hätten? Würden Sie in diesem Fall Dealloc verwenden?
Qix
4

Ich glaube ich habe eine gefunden verlässliche Antwort gefunden ! Ich musste, da die obigen Antworten mehrdeutig sind und widersprüchlich erscheinen. Ich habe Kochbücher und Programmierhandbücher durchgesehen.

Erstens der Stil von addObserver:in viewWillAppear:undremoveObserver:viewWillDisappear: funktioniert in für mich nicht (ich habe ihn getestet), da ich eine Benachrichtigung in einem untergeordneten Ansichtscontroller poste, um Code im übergeordneten Ansichtscontroller auszuführen. Ich würde diesen Stil nur verwenden, wenn ich die Benachrichtigung im selben View Controller posten und abhören würde.

Die Antwort, auf die ich mich am meisten verlassen werde, fand ich in der iOS-Programmierung: Big Nerd Ranch Guide 4th. Ich vertraue den BNR-Leuten, weil sie iOS-Schulungszentren haben und nicht nur ein weiteres Kochbuch schreiben. Es ist wahrscheinlich in ihrem besten Interesse, genau zu sein.

BNR Beispiel eins: addObserver:ininit: , removeObserver:indealloc:

BNR-Beispiel zwei: addObserver:inawakeFromNib: , removeObserver:indealloc:

… Beim Entfernen des Beobachters in dealloc: sie diese nicht[super dealloc];

Ich hoffe das hilft der nächsten Person…

Ich aktualisiere diesen Beitrag, da Apple jetzt fast vollständig auf Storyboards verzichtet hat, sodass die oben genannten möglicherweise nicht für alle Situationen gelten. Das Wichtigste (und der Grund, warum ich diesen Beitrag überhaupt hinzugefügt habe) ist, darauf zu achten, wenn Sie viewWillDisappear:angerufen werden. Es war nichts für mich, als die Anwendung in den Hintergrund trat.

Murat Zazi
quelle
Es ist schwer zu sagen, ob dies richtig ist, da der Kontext wichtig ist. Es wird bereits einige Male erwähnt, aber Dealloc macht in einem ARC-Kontext (der derzeit der einzige Kontext ist) wenig Sinn. Es ist auch nicht vorhersehbar, wann der Dealloc aufgerufen wird - viewWillDisappear ist einfacher zu steuern. Eine Randnotiz: Wenn Ihr Kind seinem Elternteil etwas mitteilen muss, klingt das Delegatenmuster nach einer besseren Wahl.
RickiG
2

Die akzeptierte Antwort ist nicht sicher und kann zu einem Speicherverlust führen. Bitte lassen Sie die Abmeldung im Dealloc, aber melden Sie sich auch in viewWillDisappear ab (das ist natürlich, wenn Sie sich in viewWillAppear registrieren). :) :)

MobileMon
quelle
1
Ich stimme dieser Antwort zu. Es treten Speicherwarnungen und Lecks auf, die nach intensiver Nutzung der App zu Abstürzen führen, wenn ich die Beobachter in viewWillDisappear nicht entferne.
SarpErdag
2

Es ist wichtig zu beachten, dass dies auch viewWillDisappearaufgerufen wird, wenn der View Controller eine neue UIView präsentiert. Dieser Delegat gibt lediglich an, dass die Hauptansicht des View Controllers auf dem Display nicht sichtbar ist.

In diesem Fall kann die Freigabe der Benachrichtigung viewWillDisappearunpraktisch sein, wenn wir die Benachrichtigung verwenden, damit die UIview mit dem übergeordneten Ansichtscontroller kommunizieren kann.

Als Lösung entferne ich den Beobachter normalerweise mit einer dieser beiden Methoden:

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

Aus ähnlichen Gründen muss ich beim ersten Ausstellen der Benachrichtigung berücksichtigen, dass jedes Mal, wenn eine Ansicht mit über dem Controller viewWillAppearangezeigt wird, die Methode ausgelöst wird. Dies erzeugt wiederum mehrere Kopien derselben Benachrichtigung. Da es keine Möglichkeit gibt, zu überprüfen, ob eine Benachrichtigung bereits aktiv ist, kann ich das Problem vermeiden, indem ich die Benachrichtigung entferne, bevor ich sie hinzufüge:

- (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];

}
Alex
quelle
-1

SWIFT 3

Es gibt zwei Fälle, in denen Benachrichtigungen verwendet werden: - Sie werden nur benötigt, wenn der Ansichts-Controller auf dem Bildschirm angezeigt wird. - Sie werden immer benötigt, auch wenn der Benutzer einen anderen Bildschirm über Strom geöffnet hat.

Für den ersten Fall ist der richtige Ort zum Hinzufügen und Entfernen von Beobachtern:

/// 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)
}

für den zweiten Fall ist der richtige Weg:

/// 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)
    }
}

Und nie setzen removeObserverin deinit{ ... }- es ist ein Fehler!

Alexander Volkov
quelle
-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)
}
Urvashi Bhagat
quelle