web-dev-qa-db-fra.com

Objectif C: Où supprimer l'observateur pour NSNotification?

J'ai une classe d'objectif C. Dans ce document, j'ai créé une méthode init et y ai configuré une NSNotification.

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

Où est-ce que je place le [[NSNotificationCenter defaultCenter] removeObserver:self] dans cette classe? Je sais que pour un UIViewController, je peux l'ajouter à la méthode viewDidUnload Alors, que faut-il faire si je viens de créer une classe Objective C?

97
Zhen

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] in méthode dealloc de ces classes, que vous avez l'intention d'utiliser comme observateurs, car c'est la dernière chance de désenregistrer un observateur proprement. Cela ne vous protégera toutefois que contre les accidents dus au fait que le centre de notification informe les objets morts. Il ne peut pas protéger votre code contre la réception de notifications, lorsque vos objets ne sont pas/ne sont plus dans un état dans lequel ils peuvent gérer correctement la notification. Pour cela ... Voir ci-dessus.

Edit (puisque la réponse semble attirer plus de commentaires que je ne l'aurais pensé) Tout ce que j'essaie de dire, c'est qu'il est très difficile de donner des conseils généraux sur le meilleur moment pour retirer 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?)
  • L'implémentation de l'observateur (quand est-il prêt à recevoir des notifications? Quand n'est-il plus prêt?)
  • Durée de vie prévue de l'observateur (est-il lié à un autre objet, par exemple une vue ou un contrôleur de vue?)
  • ...

Donc, le meilleur conseil que je puisse vous donner: protéger votre application. contre au moins un échec possible, la removeObserver: danse dans dealloc, car c’est le dernier point (dans la vie de l’objet), où vous pouvez le faire proprement. Ce que cela ne veut pas dire, c’est: "différer simplement le retrait jusqu’à ce que dealloc soit appelé, 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 le bon moment exact. Malheureusement, ne sachant pas les réponses aux questions mentionnées ci-dessus, je ne peux même pas deviner quand ce serait.

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

111
Dirk

Remarque: ceci a été testé et fonctionne à 100%

Swift

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 dans viewWillDisappear en tant que viewDidUnload la méthode est obsolète.

 [[NSNotificationCenter defaultCenter] removeObserver:observerObjectHere];

Il y a plusieurs fois son meilleur à remove observer lorsque la vue a été supprimée de la 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];
    }
}
38
Paresh Navadiya

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

37
Sebastian

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

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

En général, je le mets dans la méthode dealloc.

8
Raphael Petegrosso

Dans Swift, utilisez deinit car dealloc n’est pas disponible:

deinit {
    ...
}

Documentation rapide:

Un désinitialiseur est appelé immédiatement avant qu'une instance de classe ne soit désallouée. Vous écrivez desinitialiseurs avec le mot clé deinit, similaire à la façon dont les initiaux 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. Toutefois, lorsque vous utilisez vos propres ressources, vous devrez peut-être effectuer un nettoyage supplémentaire vous-même. 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 la classe ne soit désallouée.

7
Morten Holmgaard

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

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

Dans iOS 6, supprimer les observateurs dans viewDidUnload n'a également aucun sens, car il est désormais obsolète.

En résumé, je le fais toujours dans viewDidDisappear. Cependant, cela dépend aussi de vos besoins, comme l'a dit @Dirk.

5
kimimaro

* edit: Ce conseil s’applique à iOS <= 5 (même là, vous devriez ajouter dans viewWillAppear et supprimer dans viewWillDisappear. Toutefois, le conseil s’applique si, pour une raison quelconque, vous avez ajouté l’observateur dans viewDidLoad)

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

5
Ehren

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

Tout d'abord, le style de addObserver: dans viewWillAppear: et removeObserver: dans 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 m'appuierai le plus, que j'ai trouvée dans la programmation iOS: Big Nerd Ranch Guide 4th. Je fais confiance aux gars de la BNR parce qu'ils ont des centres de formation iOS et qu'ils ne sont pas en train d'écrire un autre livre de recettes. Il est probablement dans leur intérêt d’être précis.

Exemple de BNR un: addObserver: dans init:, removeObserver: dans dealloc:

Exemple de BNR deux: addObserver: dans awakeFromNib:, removeObserver: dans dealloc:

… Lors du retrait de l'observateur dans dealloc: ils n’utilisent pas [super dealloc];

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

Je mets à jour ce message parce que Apple a presque complètement disparu de Storyboards, de sorte que ce qui précède risque de 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 votre viewWillDisappear: se fait appeler. Ce n'était pas pour moi lorsque l'application est entrée en arrière-plan.

4
Murat Zazi

La réponse acceptée n'est pas sûre et peut provoquer une fuite de mémoire. S'il vous plaît laissez le désinscrire dans dealloc, mais aussi le désenregistrement dans viewWillDisappear (c'est bien sûr si vous vous enregistrez dans viewWillAppear) ... C'EST CE QUE JEDID TOUJOURS ET CELA FONCTIONNE! :)

2
MobileMon

Il est important de noter également que viewWillDisappear est également appelé lorsque le contrôleur de vue présente une nouvelle 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, désallouer la notification dans viewWillDisappear peut être gênant si nous utilisons la notification pour permettre à UIview de communiquer avec le contrôleur de vue parent.

En tant que solution, je supprime généralement l'observateur de l'une des deux méthodes suivantes:

- (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 je publie la notification pour la première fois, je dois prendre en compte le fait que chaque fois qu'une vue avec apparaît au-dessus du contrôleur, la méthode viewWillAppear est déclenchée. Cela générera plusieurs copies de la même notification. Puisqu'il n'y a pas moyen de vérifier si une notification est déjà active, j'élimine 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];

}
2
Alex
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)
}
0
urvashi bhagat

Swift

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 ouvre un autre écran sur le courant.

Pour le premier cas, le bon endroit pour ajouter et supprimer l'observateur est:

/// 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 bonne manière 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 ne mettez jamais removeObserver dans deinit{ ... } - c'est une erreur!

0
Alexander Volkov