Objective-C: ¿Dónde eliminar al observador para NSNotification?

102

Tengo una clase C objetiva. En él, creé un método init y configuré una NSNotification en él

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

¿Dónde configuro el [[NSNotificationCenter defaultCenter] removeObserver:self]en esta clase? Sé que para a UIViewController, puedo agregarlo al viewDidUnloadmétodo. Entonces, ¿qué se debe hacer si acabo de crear una clase c objetiva?

Zhen
fuente
Lo puse en el método dealloc.
onnoweb
1
El método dealloc no se creó automáticamente para mí cuando creé la clase objetiva c, ¿así que está bien que lo agregue?
Zhen
Sí, puede implementarlo -(void)deallocy luego agregarlo removeObserser:self. Esta es la forma más recomendada de ponerremoveObservers:self
petershine
¿Todavía está bien poner el deallocmétodo en iOS 6?
wcochran
2
Sí, está bien usar dealloc en proyectos ARC siempre que no llame a [super dealloc] (obtendrá un error del compilador si llama a [super dealloc]). Y sí, definitivamente puede poner su removeObserver en dealloc.
Phil

Respuestas:

112

La respuesta genérica sería "tan pronto como ya no necesite las notificaciones". Obviamente, esta no es una respuesta satisfactoria.

Le recomiendo que agregue una llamada [notificationCenter removeObserver: self]al método deallocde esas clases, que tiene la intención de usar como observadores, ya que es la última oportunidad para anular el registro de un observador limpiamente. Sin embargo, esto solo lo protegerá contra fallas debido a que el centro de notificaciones notifica objetos muertos. No puede proteger su código contra la recepción de notificaciones, cuando sus objetos aún no están / ya no están en un estado en el que puedan manejar correctamente la notificación. Para esto ... Ver arriba.

Editar (ya que la respuesta parece generar más comentarios de los que hubiera pensado) Todo lo que estoy tratando de decir aquí es: es realmente difícil dar consejos generales sobre cuándo es mejor eliminar al observador del centro de notificaciones, porque eso depende:

  • En su caso de uso (¿Qué notificaciones se observan? ¿Cuándo se envían?)
  • La implementación del observador (¿Cuándo está listo para recibir notificaciones? ¿Cuándo ya no está listo?)
  • El tiempo de vida previsto del observador (¿está vinculado a algún otro objeto, por ejemplo, una vista o un controlador de vista?)
  • ...

Entonces, el mejor consejo general que se me ocurre: proteger su aplicación. contra al menos un posible fracaso, removeObserver:baila dealloc, ya que ese es el último punto (en la vida del objeto), donde puedes hacerlo limpiamente. Lo que esto no significa es: "solo posponga la eliminación hasta que deallocse llame, y todo estará bien". En su lugar, elimine al observador tan pronto como el objeto ya no esté listo (o no sea necesario) para recibir notificaciones . Ese es el momento exacto. Desafortunadamente, al no conocer las respuestas a ninguna de las preguntas mencionadas anteriormente, ni siquiera puedo adivinar cuándo sería ese momento.

Siempre puede utilizar removeObserver:un objeto de forma segura varias veces (y todas menos la primera llamada con un observador determinado serán nops). Entonces: piense en hacerlo (nuevamente) deallocsolo para estar seguro, pero ante todo: hágalo en el momento apropiado (que está determinado por su caso de uso).

Puñal
fuente
4
Esto no es seguro con ARC y podría causar una fuga. Vea esta sesión de disco: cocoabuilder.com/archive/cocoa/311831-arc-and-dealloc.html
MobileMon
3
@MobileMon El artículo al que vinculó parece demostrar mi punto. Qué me estoy perdiendo ?
Dirk
Supongo que debe tenerse en cuenta que se debe eliminar al observador en otro lugar que no sea dealloc. Por ejemplo, viewwilldisappear
MobileMon
1
@MobileMon: sí. Espero que ese sea el punto que estoy transmitiendo con mi respuesta. Eliminar al observador dealloces solo una última línea de defensa contra el bloqueo de la aplicación debido a un acceso posterior a un objeto descalificado. Pero el lugar adecuado para anular el registro de un observador suele ser otro lugar (y, a menudo, mucho antes en el ciclo de vida del objeto). No estoy tratando de decir aquí "Oye, hazlo deallocy todo saldrá bien".
Dirk
@MobileMon "Por ejemplo, viewWillDisappear" El problema de dar un consejo concreto es que realmente depende de qué tipo de objeto registre como observador para qué tipo de evento. Se puede ser la solución correcta para anular el registro de observador en viewWillDisappear(o viewDidUnload) para UIViewControllers, pero que realmente depende del caso de uso.
Dirk
39

Nota: Esto ha sido probado y funciona al 100%.

Rápido

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

PresentadoViewController

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

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

C objetivo

En iOS 6.0 > version, es mejor eliminar al observador viewWillDisappearya que el viewDidUnloadmétodo está en desuso.

 [[NSNotificationCenter defaultCenter] removeObserver:observerObjectHere];

Muchas veces es mejor remove observercuando la vista se ha eliminado del 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];
    }
}

PresentadoViewController

- (void)viewWillDisappear:(BOOL)animated{
    if ([self isBeingDismissed] == YES) ///presented view controller
    {
        // remove observer here
        [[NSNotificationCenter defaultCenter] removeObserver:observerObjectHere];
    }
}
Paresh Navadiya
fuente
8
Excepto que un controlador aún puede querer notificaciones cuando su vista no se muestra (por ejemplo, para recargar un tableView).
wcochran
2
@wcochran recarga / actualiza automáticamente enviewWillAppear:
Richard
@Prince, ¿puedes explicar por qué viewWillDisapper es mejor que dealloc? así que hemos agregado observador a self, por lo que cuando el self se elimine de la memoria, llamará dealloc y luego se eliminarán todos los observadores, ¿no es una buena lógica?
Matrosov Alexander
Es casi seguro que llamar removeObserver:selfa cualquiera de los UIViewControllereventos del ciclo de vida arruinará su semana. Más lectura: subjetivo-objetivo-c.blogspot.com/2011/04/…
cbowns
1
Realizar las removeObserverllamadas viewWillDisappearcomo se indica es definitivamente el camino correcto si el controlador se presenta a través de pushViewController. Si los coloca en su dealloclugar dealloc, nunca se llamará, al menos en mi experiencia ...
Christopher King
38

Desde iOS 9 ya no es necesario eliminar observadores.

En OS X 10.11 e iOS 9.0, NSNotificationCenter y NSDistributedNotificationCenter ya no enviarán notificaciones a los observadores registrados que puedan ser desasignados.

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

Sebastián
fuente
2
Puede que no envíen mensajes a los observadores, pero creo que mantendrán una fuerte referencia a ellos, según tengo entendido. En ese caso, todos los observadores permanecerán en la memoria y producirán una fuga. Corríjame si me equivoco.
abeto
6
La documentación vinculada entra en detalles sobre eso. TL; DR: es una referencia débil.
Sebastian
pero, por supuesto, sigue siendo necesario en caso de que mantenga el objeto que hace referencia a ellos y simplemente no quiera escuchar más las notificaciones
TheEye
25

Si el observador se agrega a un controlador de vista , recomiendo encarecidamente agregarlo viewWillAppeary eliminarlo viewWillDisappear.

RickiG
fuente
Tengo curiosidad, @RickiG: ¿por qué recomienda usar viewWillAppeary viewWillDisappearpara viewControllers?
Isaac Overacker
2
@IsaacOveracker algunas razones: su código de configuración (por ejemplo, loadView y viewDidLoad) podría causar que las notificaciones se activen y su controlador debe reflejar eso antes de que se muestre. Si lo hace así, hay algunos beneficios. En el momento en que decidió "dejar" el controlador, no le importan las notificaciones y no harán que haga lógica mientras el controlador está siendo empujado fuera de la pantalla, etc. Hay casos especiales en los que el controlador debería recibir notificaciones cuando está fuera de la pantalla, supongo que no puedes hacer esto. Pero eventos como ese probablemente deberían estar en su modelo.
RickiG
1
@IsaacOveracker también con ARC sería extraño implementar dealloc para cancelar la suscripción a las notificaciones.
RickiG
4
De los que he probado, con iOS7 esta es la mejor manera de registrar / eliminar observadores cuando se trabaja con UIViewControllers. El único inconveniente es que, en muchos casos, no desea que se elimine el observador al usar UINavigationController y enviar otro UIViewController a la pila. Solución: puede comprobar si el VC aparece en viewWillDisappear llamando a [self isBeingDismissed].
lekksi
Es posible que sacar el controlador de vista desde el controlador de navegación no provoque deallocuna llamada inmediata. Volver al controlador de vista puede causar múltiples notificaciones si se agrega observador en los comandos de inicialización.
Jonathan Lin
20
-(void) dealloc {
      [[NSNotificationCenter defaultCenter] removeObserver:self];
      [super dealloc];
}
Legolas
fuente
4
Cambiaría el orden de estas instrucciones ... Usar selfafter [super dealloc]me pone nervioso ... (incluso si es poco probable que el receptor elimine la referencia del puntero de alguna manera, bueno, nunca se sabe cómo se implementaron NSNotificationCenter)
Dirk
Hm. Esto ha funcionado para mí. ¿Ha notado algún comportamiento inusual?
Legolas
1
Dirk tiene razón, esto es incorrecto. [super dealloc]debe ser siempre la última declaración de su deallocmétodo. Destruye tu objeto; después de que se ejecute, ya no tendrá un selfarchivo. / cc @Dirk
jscs
38
Si usa ARC en iOS 5+, creo que [super dealloc]ya no es necesario
pixelfreak
3
@pixelfreak más fuerte, ARC no permite llamar a [super dealloc]
tapmonkey
8

En general, lo pongo en el deallocmétodo.

Rafael Petegrosso
fuente
7

En uso rápido deinit porque dealloc no está disponible:

deinit {
    ...
}

Documentación rápida:

Se llama a un desinicializador inmediatamente antes de que se desasigne una instancia de clase. Los desinicializadores se escriben con la palabra clave deinit, de forma similar a como se escriben los inicializadores con la palabra clave init. Los desinicializadores solo están disponibles en tipos de clase.

Normalmente, no es necesario realizar una limpieza manual cuando se desasignan las instancias. Sin embargo, cuando esté trabajando con sus propios recursos, es posible que deba realizar una limpieza adicional usted mismo. Por ejemplo, si crea una clase personalizada para abrir un archivo y escribir algunos datos en él, es posible que deba cerrar el archivo antes de que se desasigne la instancia de la clase.

Morten Holmgaard
fuente
5

* editar: este consejo se aplica a iOS <= 5 (incluso allí debería agregar viewWillAppeary eliminar viewWillDisappear, sin embargo, el consejo se aplica si por alguna razón ha agregado el observador viewDidLoad)

Si ha agregado el observador en viewDidLoad, debe eliminarlo en ambos deallocy viewDidUnload. De lo contrario, terminará agregándolo dos veces cuando viewDidLoadse llame después viewDidUnload(esto sucederá después de una advertencia de memoria). Esto no es necesario en iOS 6, donde viewDidUnloadestá obsoleto y no se llamará (porque las vistas ya no se descargan automáticamente).

Ehren
fuente
2
Bienvenido a StackOverflow. Consulte las preguntas frecuentes de MarkDown (icono de signo de interrogación junto al cuadro de edición de preguntas / respuestas). El uso de Markdwon mejorará la usabilidad de su respuesta.
marko
5

En mi opinión, el siguiente código no tiene sentido en ARC :

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

En iOS 6 , tampoco tiene sentido eliminar observadores viewDidUnload, porque ahora ha quedado obsoleto.

En resumen, siempre lo hago viewDidDisappear. Sin embargo, también depende de sus requisitos, como dijo @Dirk.

kimimaro
fuente
Mucha gente todavía está escribiendo código para versiones anteriores de iOS que iOS6 .... :-)
lnafziger
En ARC puedes usar este código pero sin la línea [super dealloc]; Puede ver más aquí: developer.apple.com/library/ios/#releasenotes/ObjectiveC/…
Alex
1
¿Qué pasaría si un NSObject habitual fuera el observador de una notificación? ¿Usaría dealloc en este caso?
qix
4

¡Creo que encontré una respuesta confiable ! Tuve que hacerlo, ya que las respuestas anteriores son ambiguas y parecen contradictorias. Revisé libros de cocina y guías de programación.

Primero, el estilo de addObserver:in viewWillAppear:y removeObserver:in viewWillDisappear:no me funciona (lo probé) porque estoy publicando una notificación en un controlador de vista secundaria para ejecutar código en el controlador de vista principal. Solo usaría este estilo si estuviera publicando y escuchando la notificación dentro del mismo controlador de vista.

La respuesta en la que más confiaré, la encontré en Programación de iOS: Guía de Big Nerd Ranch 4th. Confío en los chicos de BNR porque tienen centros de capacitación de iOS y no solo están escribiendo otro libro de cocina. Probablemente les conviene ser precisos.

Ejemplo uno de BNR: addObserver:en init:, removeObserver:endealloc:

Ejemplo dos de BNR: addObserver:en awakeFromNib:, removeObserver:endealloc:

... cuando quitan al observador en dealloc:no usan[super dealloc];

Espero que esto ayude a la próxima persona ...

Estoy actualizando esta publicación porque Apple ahora se ha ido casi por completo con Storyboards, por lo que es posible que lo mencionado anteriormente no se aplique a todas las situaciones. Lo importante (y la razón por la que agregué esta publicación en primer lugar) es prestar atención si te viewWillDisappear:llaman. No fue para mí cuando la aplicación entró en segundo plano.

Murat Zazi
fuente
Es difícil decir si esto es correcto ya que el contexto es importante. Ya se menciona algunas veces, pero dealloc tiene poco sentido en un contexto ARC (que es el único contexto por ahora). Tampoco es predecible cuando se llama a dealloc: viewWillDisappear es más fácil de controlar. Una nota al margen: si su hijo necesita comunicar algo a sus padres, el patrón de delegado parece una mejor opción.
RickiG
2

La respuesta aceptada no es segura y podría provocar una pérdida de memoria. Por favor, deje la cancelación del registro en dealloc pero también cancele el registro en viewWillDisappear (eso es, por supuesto, si se registra en viewWillAppear) .... ¡ESO ES LO QUE HICE DE TODOS MODOS Y FUNCIONA MUY BIEN! :)

MobileMon
fuente
1
Estoy de acuerdo con esta respuesta. Experimento advertencias y fugas de memoria que provocan bloqueos después del uso intensivo de la aplicación si no elimino los observadores en viewWillDisappear.
SarpErdag
2

Es importante notar también que también viewWillDisappearse llama cuando el controlador de vista presenta una nueva UIView. Este delegado simplemente indica que la vista principal del controlador de vista no está visible en la pantalla.

En este caso, desasignar la notificación en viewWillDisappearpuede ser un inconveniente si usamos la notificación para permitir que UIview se comunique con el controlador de vista principal.

Como solución, generalmente elimino al observador en uno de estos dos métodos:

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

Por razones similares, cuando emito la notificación por primera vez, debo tener en cuenta el hecho de que cada vez que aparece una vista sobre el controlador, se activa el viewWillAppearmétodo. Esto, a su vez, generará copias múltiples de la misma notificación. Dado que no hay una forma de verificar si una notificación ya está activa, obvio el problema eliminando la notificación antes de agregarla:

- (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
fuente
-1

SWIFT 3

Hay dos casos de uso de notificaciones: - solo se necesitan cuando el controlador de vista está en la pantalla; - Son necesarios siempre, incluso si el usuario abrió otra pantalla sobre la corriente.

Para el primer caso, el lugar correcto para agregar y quitar observador es:

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

para el segundo caso, la forma correcta es:

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

Y nunca puso removeObserveren deinit{ ... }- que es un error!

Alexander Volkov
fuente
-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
fuente