Objectif-C: Où retirer l'observateur pour NSNotification?

102

J'ai une classe C objective. Dans celui-ci, j'ai créé une méthode init et mis en place une NSNotification dedans

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

Où dois-je placer le [[NSNotificationCenter defaultCenter] removeObserver:self]dans cette classe? Je sais que pour a UIViewController, je peux l'ajouter dans la viewDidUnloadméthode Alors que faut-il faire si je viens de créer une classe objectif c?

Zhen
la source
Je l'ai mis dans la méthode dealloc.
onnoweb
1
La méthode dealloc n'a pas été créée automatiquement pour moi lorsque j'ai créé la classe objective c, donc je peux l'ajouter?
Zhen
Oui, vous pouvez l'implémenter -(void)dealloc, puis l'ajouter removeObserser:self. C'est le moyen le plus recommandé de mettreremoveObservers:self
petershine
Est-il toujours acceptable de mettre la deallocméthode dans iOS 6?
wcochran
2
Oui, vous pouvez utiliser dealloc dans les projets ARC tant que vous n'appelez pas [super dealloc] (vous obtiendrez une erreur de compilation si vous appelez [super dealloc]). Et oui, vous pouvez définitivement mettre votre removeObserver dans dealloc.
Phil

Réponses:

112

La réponse générique serait «dès que vous n’avez plus besoin des notifications». Ce n’est évidemment pas une réponse satisfaisante.

Je vous recommande d'ajouter un appel [notificationCenter removeObserver: self]à la méthode deallocde ces classes, que vous avez l'intention d'utiliser en tant qu'observateurs, car c'est la dernière chance de désinscrire proprement un observateur. Cependant, cela ne vous protégera que contre les plantages dus à la notification des objets morts par le centre de notification. Il ne peut pas protéger votre code contre la réception de notifications, lorsque vos objets ne sont pas encore / plus dans un état dans lequel ils peuvent gérer correctement la notification. Pour cela ... Voir ci-dessus.

Modifier (puisque la réponse semble attirer plus de commentaires que je ne l'aurais pensé) Tout ce que j'essaie de dire ici, c'est: il est vraiment difficile de donner des conseils généraux sur le meilleur moment pour supprimer l'observateur du centre de notification, car cela dépend:

  • Sur votre cas d'utilisation (Quelles notifications sont observées? Quand sont-elles envoyées?)
  • La mise en œuvre de l'observateur (Quand est-il prêt à recevoir des notifications? Quand n'est-il plus prêt?)
  • La durée de vie prévue de l'observateur (est-elle liée à un autre objet, par exemple, une vue ou un contrôleur de vue?)
  • ...

Donc, le meilleur conseil général que je puisse donner: protéger votre application. contre au moins un échec possible, faites la removeObserver:danse dealloc, car c'est le dernier point (dans la vie de l'objet), où vous pouvez le faire proprement. Cela ne veut pas dire: "reporter simplement la suppression jusqu'à ce qu'elle deallocsoit appelée, et tout ira bien". Au lieu de cela, supprimez l'observateur dès que l'objet n'est plus prêt (ou requis) pour recevoir des notifications . C'est exactement le bon moment. Malheureusement, ne connaissant pas les réponses à aucune des questions mentionnées ci-dessus, je ne peux même pas deviner quand ce moment serait.

Vous pouvez toujours en toute sécurité removeObserver:un objet plusieurs fois (et tous sauf le tout premier appel avec un observateur donné seront nops). Donc: pensez à le faire (à nouveau) deallocjuste pour être sûr, mais avant tout: faites-le au moment approprié (qui est déterminé par votre cas d'utilisation).

Poignard
la source
4
Ceci n'est pas sûr avec l'ARC et pourrait potentiellement provoquer une fuite. Voir cette discussion: cocoabuilder.com/archive/cocoa/311831-arc-and-dealloc.html
MobileMon
3
@MobileMon L'article auquel vous avez lié semble faire valoir mon point de vue. Qu'est-ce que je rate ?
Dirk
Je suppose qu'il convient de noter que l'on devrait supprimer observer ailleurs que dealloc. Par exemple, viewwilldisappear
MobileMon
1
@MobileMon - oui. J'espère que c'est ce que je veux dire avec ma réponse. La suppression de l'observateur dans deallocn'est qu'une dernière ligne de défense contre le plantage de l'application en raison d'un accès ultérieur à un objet décalloué. Mais le bon endroit pour désinscrire un observateur est généralement ailleurs (et souvent, beaucoup plus tôt dans le cycle de vie de l'objet). Je n'essaye pas de dire ici "Hé, fais-le deallocet tout ira bien".
Dirk
@MobileMon "Par exemple, viewWillDisappear" Le problème de donner un conseil concret est que cela dépend vraiment du type d'objet que vous enregistrez en tant qu'observateur pour quel type d'événement. Cela peut être la bonne solution pour désinscrire un observateur dans viewWillDisappear(ou viewDidUnload) pour UIViewControllers, mais cela dépend vraiment du cas d'utilisation.
Dirk
39

Remarque: cela a été testé et fonctionne à 100% pour cent

Rapide

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

Objectif c

Dans iOS 6.0 > version, il est préférable de supprimer l'observateur viewWillDisappearcar la viewDidUnloadméthode est obsolète.

 [[NSNotificationCenter defaultCenter] removeObserver:observerObjectHere];

Il est souvent préférable de remove observersupprimer la vue du 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
la source
8
Sauf qu'un contrôleur peut toujours vouloir des notifications lorsque sa vue ne s'affiche pas (par exemple pour recharger une tableView).
wcochran
2
@wcochran recharger / actualiser automatiquement dansviewWillAppear:
Richard
@Prince pouvez-vous expliquer pourquoi viewWillDisapper est meilleur que dealloc? nous avons donc ajouté un observateur à soi, donc lorsque le soi sera supprimé de la mémoire, il appellera dealloc et ensuite tous les observateurs seront supprimés, n'est-ce pas une bonne logique.
Matrosov Alexander le
L'appel removeObserver:selfà l'un des UIViewControllerévénements du cycle de vie est presque garanti de ruiner votre semaine. More reading: subjectif-objective-c.blogspot.com/2011/04/…
cbowns
1
Mettre les removeObserverappels viewWillDisappearcomme indiqué est certainement la bonne façon de procéder si le contrôleur est présenté via pushViewController. Si vous les mettez à la deallocplace dealloc, vous ne serez jamais appelé - du moins d'après mon expérience ...
Christopher King
38

Depuis iOS 9, il n'est plus nécessaire de supprimer les observateurs.

Sous OS X 10.11 et iOS 9.0, NSNotificationCenter et NSDistributedNotificationCenter n'enverront plus de notifications aux observateurs enregistrés susceptibles d'être désalloués.

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

Sébastien
la source
2
Peut-être n'enverront-ils pas de messages aux observateurs, mais je crois qu'ils garderont une forte référence à eux si je comprends bien. Dans ce cas, tous les observateurs resteront en mémoire et produiront une fuite. Corrigez-moi si je me trompe.
sapin
6
La documentation liée entre dans les détails à ce sujet. TL; DR: c'est une référence faible.
Sebastian
mais bien sûr, c'est toujours nécessaire au cas où vous garderiez l'objet de les référencer et que vous ne vouliez plus écouter les notifications
TheEye
25

Si l'observateur est ajouté à un contrôleur de vue , je recommande fortement de l'ajouter viewWillAppearet de le supprimer viewWillDisappear.

RickiG
la source
Je suis curieux, @RickiG: pourquoi recommandez-vous d'utiliser viewWillAppearet viewWillDisappearpour viewControllers?
Isaac Overacker
2
@IsaacOveracker quelques raisons: votre code de configuration (par exemple loadView et viewDidLoad) pourrait potentiellement provoquer le déclenchement des notifications et votre contrôleur doit en tenir compte avant de s'afficher. Si vous le faites comme ça, il y a quelques avantages. Au moment où vous avez décidé de "quitter" le contrôleur, vous ne vous souciez pas des notifications et elles ne vous obligeront pas à faire de la logique pendant que le contrôleur est poussé hors de l'écran, etc. Il y a des cas particuliers où le contrôleur devrait recevoir des notifications quand c'est hors écran, je suppose que vous ne pouvez pas faire ça. Mais des événements comme celui-là devraient probablement figurer dans votre modèle.
RickiG
1
@IsaacOveracker également avec ARC, il serait étrange d'implémenter dealloc pour se désabonner des notifications.
RickiG
4
Parmi ceux que j'ai essayés, avec iOS7, c'est le meilleur moyen d'enregistrer / supprimer des observateurs lorsque vous travaillez avec UIViewControllers. Le seul problème est que, dans de nombreux cas, vous ne voulez pas que l'observateur soit supprimé lors de l'utilisation de UINavigationController et du transfert d'un autre UIViewController vers la pile. Solution: Vous pouvez vérifier si le VC est affiché dans viewWillDisappear en appelant [self isBeingDismissed].
lekksi
Faire sauter le contrôleur de vue depuis le contrôleur de navigation peut ne pas entraîner deallocl'appel immédiat. Revenir dans le contrôleur de vue peut alors provoquer plusieurs notifications si l'observateur est ajouté dans les commandes d'initialisation.
Jonathan Lin
20
-(void) dealloc {
      [[NSNotificationCenter defaultCenter] removeObserver:self];
      [super dealloc];
}
Legolas
la source
4
Je changerais l'ordre de ces instructions ... Utiliser selfafter [super dealloc]me rend nerveux ... (même s'il est peu probable que le récepteur déréférencera le pointeur de quelque manière que ce soit, eh bien, on ne sait jamais comment ils ont mis en œuvre NSNotificationCenter)
Dirk
Hm. ça a marché pour moi. Avez-vous remarqué un comportement inhabituel?
Legolas
1
Dirk a raison - c'est incorrect. [super dealloc]doit toujours être la dernière déclaration de votre deallocméthode. Il détruit votre objet; après son exécution, vous n'avez selfplus de fichier valide . / cc @Dirk
jscs
38
Si vous utilisez ARC sur iOS 5+, je pense que ce [super dealloc]n'est plus nécessaire
pixelfreak
3
@pixelfreak plus fort, il n'est pas autorisé sous ARC d'appeler [super dealloc]
tapmonkey
8

En général, je l'ai mis dans la deallocméthode.

Raphael Petegrosso
la source
7

Dans swift use deinit car dealloc n'est pas disponible:

deinit {
    ...
}

Documentation Swift:

Un désinitialiseur est appelé immédiatement avant qu'une instance de classe ne soit libérée. Vous écrivez des désinitialiseurs avec le mot-clé deinit, de la même manière que les initiateurs sont écrits avec le mot-clé init. Les déinitialiseurs ne sont disponibles que sur les types de classe.

En règle générale, vous n'avez pas besoin d'effectuer un nettoyage manuel lorsque vos instances sont désallouées. Cependant, lorsque vous travaillez avec vos propres ressources, vous devrez peut-être effectuer vous-même un nettoyage supplémentaire. Par exemple, si vous créez une classe personnalisée pour ouvrir un fichier et y écrire des données, vous devrez peut-être fermer le fichier avant que l'instance de classe ne soit libérée.

Morten Holmgaard
la source
5

* modifier: Ce conseil s'applique à iOS <= 5 (même là, vous devriez ajouter viewWillAppearet supprimer viewWillDisappear- cependant le conseil s'applique si pour une raison quelconque vous avez ajouté l'observateur dansviewDidLoad )

Si vous avez ajouté l'observateur dans, viewDidLoadvous devez le supprimer à la fois dans deallocet viewDidUnload. Sinon, vous finirez par l'ajouter deux fois lors de l' viewDidLoadappel après viewDidUnload(cela se produira après un avertissement de mémoire). Cela n'est pas nécessaire dans iOS 6 où viewDidUnloadest obsolète et ne sera pas appelé (car les vues ne sont plus automatiquement déchargées).

Ehren
la source
2
Bienvenue dans StackOverflow. Veuillez consulter la FAQ MarkDown (icône de point d'interrogation à côté de la zone d'édition de question / réponse). L'utilisation de Markdwon améliorera la convivialité de votre réponse.
marko
5

À mon avis, le code suivant n'a aucun sens dans ARC :

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

Dans iOS 6 , il est également inutile de supprimer des observateurs viewDidUnload, car il est désormais obsolète.

Pour résumer, je le fais toujours viewDidDisappear. Cependant, cela dépend également de vos besoins, tout comme @Dirk l'a dit.

Kimimaro
la source
Beaucoup de gens écrivent encore du code pour les anciennes versions d'iOS qu'iOS6 .... :-)
lnafziger
Dans ARC, vous pouvez utiliser ce code mais sans la ligne [super dealloc]; Vous pouvez en voir plus ici: developer.apple.com/library/ios/#releasenotes/ObjectiveC/…
Alex
1
Et si vous aviez un NSObject régulier pour observer une notification? Utiliseriez-vous dealloc dans ce cas?
qix
4

Je pense avoir trouvé une réponse fiable ! Je devais le faire, car les réponses ci-dessus sont ambiguës et semblent contradictoires. J'ai parcouru les livres de cuisine et les guides de programmation.

Premièrement, le style de addObserver:in viewWillAppear:et removeObserver:in viewWillDisappear:ne fonctionne pas pour moi (je l'ai testé) car je publie une notification dans un contrôleur de vue enfant pour exécuter du code dans le contrôleur de vue parent. Je n'utiliserais ce style que si je postais et écoutais la notification dans le même contrôleur de vue.

La réponse sur laquelle je vais le plus compter, j'ai trouvé dans la programmation iOS: Big Nerd Ranch Guide 4th. Je fais confiance aux gars de BNR car ils ont des centres de formation iOS et ils n'écrivent pas simplement un autre livre de cuisine. Il est probablement dans leur meilleur intérêt d'être précis.

Exemple 1 BNR: addObserver:dans init:, removeObserver:dansdealloc:

Exemple BNR deux: addObserver:in awakeFromNib:, removeObserver:indealloc:

… Lors de la suppression de l'observateur, dealloc:ils n'utilisent pas[super dealloc];

J'espère que cela aidera la prochaine personne…

Je mets à jour cet article parce qu'Apple a maintenant presque complètement abandonné les Storyboards, donc ce qui précède peut ne pas s'appliquer à toutes les situations. La chose importante (et la raison pour laquelle j'ai ajouté ce message en premier lieu) est de faire attention si vous viewWillDisappear:êtes appelé. Ce n'était pas pour moi lorsque l'application est entrée en arrière-plan.

Murat Zazi
la source
Il est difficile de dire si cela est juste car le contexte est important. Il est déjà mentionné à quelques reprises, mais dealloc n'a guère de sens dans un contexte ARC (qui est le seul contexte actuellement). Il n'est pas non plus prévisible lorsque dealloc est appelé - viewWillDisappear est plus facile à contrôler. Remarque: si votre enfant a besoin de communiquer quelque chose à son parent, le modèle de délégué semble être un meilleur choix.
RickiG
2

La réponse acceptée n'est pas sûre et pourrait provoquer une fuite de mémoire. S'il vous plaît laissez le désenregistrer dans dealloc mais aussi désenregistrer dans viewWillDisappear (c'est bien sûr si vous vous inscrivez dans viewWillAppear) .... C'EST CE QUE J'AI FAIT DE TOUTE MANIÈRE ET CELA FONCTIONNE BIEN! :)

MobileMon
la source
1
Je suis d'accord avec cette réponse. Je rencontre des avertissements de mémoire et des fuites conduisant à des plantages après une utilisation intensive de l'application si je ne supprime pas les observateurs dans viewWillDisappear.
SarpErdag
2

Il est important de noter également qu'il viewWillDisappearest également appelé lorsque le contrôleur de vue présente un nouvel UIView. Ce délégué indique simplement que la vue principale du contrôleur de vue n'est pas visible à l'écran.

Dans ce cas, la désallocation de la notification dans viewWillDisappearpeut être gênante si nous utilisons la notification pour permettre à UIview de communiquer avec le contrôleur de vue parent.

En guise de solution, je supprime généralement l'observateur dans l'une de ces deux méthodes:

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

Pour des raisons similaires, lorsque j'émets la notification la première fois, je dois tenir compte du fait qu'à chaque fois qu'une vue apparaît au-dessus du contrôleur, la viewWillAppearméthode est déclenchée. Cela générera à son tour plusieurs copies de la même notification. Comme il n'y a pas de moyen de vérifier si une notification est déjà active, j'évite le problème en supprimant la notification avant de l'ajouter:

- (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
la source
-1

SWIFT 3

Il existe deux cas d'utilisation des notifications: - elles ne sont nécessaires que lorsque le contrôleur de vue est à l'écran; - ils sont toujours nécessaires, même si l'utilisateur a ouvert un autre écran sur courant.

Pour le premier cas, les emplacements corrects pour ajouter et supprimer un observateur sont:

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

pour le second cas, la manière correcte est:

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

Et jamais mis removeObserveren deinit{ ... }- c'est une erreur!

Alexandre Volkov
la source
-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
la source