NSNotificationCenter atrapando y rastreando todas las NSNOTIFICACIONES


Para una mejor comprensión de lo que sucede "bajo el capó", me encantaría hacer un seguimiento completo de cualquier notificación que ocurra dentro de mi aplicación.

Ingenuo como soy, lo primero que intenté fue registrarme así:

En algún lugar de mi aplicación:

{
    [...]
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(traceNotifications:) name:nil object:nil];
    [...]
}

- (void)traceNotifications:(NSNotification *)notification
{
    NSLog(@"received notification %@", [notification name]);
}

En realidad recibo una serie de notificaciones de esa manera. Pero en algún momento la aplicación se bloquea. El seguimiento de la pila muestra que está fallando con EXC_BAD_ACCESS en realizeClass, que desde mi experiencia lo hace indica que algo es llamado después de su desasignación. Mi objeto de observación sin embargo todavía está vivo, su deallocator no ha sido llamado (todavía).

Lo siguiente que intenté fue establecer un punto de interrupción hacia -[NSNotificationCenter postNotification:] y luego ejecutar po {NSNotification *}($ebp+16) dentro de la consola gdb siempre que mi punto de interrupción esté atrapado. Eso reveló algunas notificaciones, pero no todo lo que estoy esperando/esperando. Por ejemplo, mi aplicación maneja los cambios de orientación correctamente, pero no veo ninguna notificación atrapada cuando reorientar el dispositivo (en el simulador).

¿Qué me estoy perdiendo? ¿Existe alguna forma (por ejemplo, una herramienta) de observar de forma fiable un NSNotificationCenter?

Gracias por cualquier sugerencia.

Author: Till, 2010-09-16

4 answers

La única solución que conseguí para trabajar fue el uso de puntos de interrupción.

He añadido un punto de interrupción en __CFXNotificationPost_old (CoreFoundation) y lo he incluido con un comando Depurador po {NSNotification *}($ebp+12). Todo esto es muy fácil de hacer dentro de la interfaz gráfica de Xcode:

  • haga clic en "Ejecutar" en el menú de la aplicación Xcode (parte superior de la pantalla)
  • seleccione "Depurador"
  • dentro de la ventana del depurador, haga clic en"Show-Breakpoints"
  • haga clic en la línea "Enter Symbol-Name" e ingrese "_ _ CFXNotificationPost _ old"
  • haga clic en
  • en el "+" en el lado derecho
  • seleccione "Comando depurador" en esa lista desplegable
  • enter " po {NSNotification*} (eb ebp+12)
  • (es posible que también desee activar el registro marcando la casilla de verificación "Registro" en la parte inferior)
  • ejecute su aplicación en una sesión de depuración del simulador desde Xcode

La aplicación detendrá su ejecución cada vez que se publique una NSNotification y la mostrará dentro de la consola gdb.

Intenté crear un punto de seguimiento dentro de gdb pero fallé debido a que las acciones de tracepoint dentro de Xcode gdb parecen tener errores - o tal vez soy demasiado stoopid para hacerlos funcionar.

También traté de crear un script de Dtrace de Instrumentos personalizados, pero fallé ya que mi Dtrace Karate no es lo suficientemente fuerte.

Si logras que alguna de las últimas opciones funcione, por favor sigue adelante y publícalas como una respuesta alternativa: votaré y las marcaré como la favorita.

UPDATE

Años después de esta pregunta, encontré la derecha forma de atrapar todas las notificaciones en el nivel de CoreFoundation.

Así es como se puede hacer:

void MyCallBack (CFNotificationCenterRef center,
                 void *observer,
                 CFStringRef name,
                 const void *object,
                 CFDictionaryRef userInfo)
{
    NSLog(@"name: %@", name);
    NSLog(@"userinfo: %@", userInfo);
}

CFNotificationCenterAddObserver(CFNotificationCenterGetLocalCenter(), 
    NULL, 
    MyCallBack, 
    NULL, 
    NULL,  
    CFNotificationSuspensionBehaviorDeliverImmediately);

En realidad me siento un poco avergonzado de no haber mirado más de cerca la interfaz de CoreFoundation antes.

 57
Author: Till,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-12-18 22:08:12

Hey Till-Yo uso las notificaciones mucho, y tuve algunos problemas serios para depurarlas también. Recientemente lancé una aplicación llamada Spark Inspector ( http://sparkinspector.com / ) eso hace que el proceso sea un poco más fácil. Agregas un framework a tu app, y hace swizzles a NSNotificationCenter para que puedas ver una tabla de todas las notificaciones enviadas y recibidas en y nuestra app, con los rastros de pila donde fueron enviadas y la lista de todos los métodos que las observaron. Sé que es sobre tres años tarde, pero puede ayudar!

introduzca la descripción de la imagen aquí

 20
Author: Ben Gotow,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-15 05:31:15

Sé que la pregunta publicada es antigua, pero pensé que respondería con un par de líneas de código.
Puedes ver todas las notificaciones publicadas mientras tu aplicación se está ejecutando a través de este bloque:

  [[NSNotificationCenter defaultCenter] addObserverForName:nil
                                                    object:nil
                                                     queue:nil
                                                 usingBlock:^(NSNotification *notification) {
    NSLog(@"%@", notification.name);
  }];

Agregue eso al método viewWillAppear de un controlador de vista apropiado. (Por supuesto, debe eliminar esto de su código al preparar su aplicación para cualquier tipo de distribución.)

También, asegúrese de añadir esto:

[[NSNotificationCenter defaultCenter] removeObserver:self];

Al correspondiente controlador de vista seleccionado Método viewWillDisappear.

ACTUALIZAR: La misma respuesta, pero en Swift:

override func viewWillAppear(animated: Bool) {
  super.viewWillAppear(animated)
  NSNotificationCenter.defaultCenter().addObserverForName(nil, 
                                                  object: nil, 
                                                   queue: nil) { 
                                                     note in
    print(note.name + "\r\n")
  }
}

override func viewWillDisappear(animated: Bool) {
  NSNotificationCenter.defaultCenter().removeObserver(self)
  super.viewWillDisappear(animated)
}
 15
Author: Mark Semsel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-13 18:13:28

Para fines de depuración, encuentro que un punto de interrupción es realmente mejor que agregar código al proyecto. Sin embargo, la solución de@Till no parecía funcionar para mí. Encontré otra solución en línea, y la ajusté un poco.

Punto de interrupción simbólico

  • Símbolo : -[NSNotificationCenter postNotificationName:object:userInfo:]
  • Condición : ((NSRange)[$arg3 rangeOfString:@"^(_|NS|UI)" options:1024]).length == 0
  • Acción : Orden del depurador po $arg3
  • Continuar automáticamente después evaluación de acciones

Notas:

  • La condición impide cualquier notificación que comience con _, NS, o UI de ser mostrado.
  • 1024 se refiere a NSRegularExpressionSearch, que no parece estar disponible para ese punto de interrupción.
  • Utilizo .length == 0 en lugar de .location == NSNotFound porque NSNotFound parece evaluar un valor diferente (-1 o (NSUInteger)18446744073709551615) que el valor devuelto en este punto de interrupción (9223372036854775807).
 3
Author: Senseful,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:00:35