35 votes

NSNotificationCenter piégeant et traçant toutes les NSNotifications

Pour mieux comprendre ce qui se passe "sous le capot", j'aimerais faire une trace complète de toutes les notifications qui se produisent dans mon application.

Naïf comme je suis, la première chose que j'ai essayée était de m'enregistrer comme ça :

Quelque part dans mon application :

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

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

Je reçois effectivement un certain nombre de notifications de cette façon. Mais à un moment donné, l'application se plante. La trace de la pile montre qu'elle se plante avec EXC_BAD_ACCESS dans realizeClass, ce qui, d'après mon expérience, indique que quelque chose est appelé après sa désallocation. Mon objet d'observation est cependant toujours vivant, son deallocator n'a pas été appelé (encore).

La chose suivante que j'ai essayée était de mettre un point d'arrêt vers -[NSNotificationCenter postNotification:] et ensuite exécuter po {NSNotification *}($ebp+16) dans la console gdb chaque fois que mon point d'arrêt est piégé. Cela a révélé quelques notifications, mais pas toutes celles que j'attendais/ espérais. Par exemple, mon application gère correctement les changements d'orientation mais je ne vois pas de notifications piégées lors de la réorientation du dispositif (dans le simulateur).

Qu'est-ce que je rate ? Existe-t-il un moyen (par exemple un outil) pour observer de manière fiable un NSNotificationCenter ?

Merci pour tout conseil.

60voto

Till Points 20820

La seule solution que j'ai trouvée pour fonctionner était d'utiliser des points d'arrêt.

J'ai ajouté un point d'arrêt à __CFXNotificationPost_old (CoreFoundation) et l'a associé à une commande de débogage. po {NSNotification *}($ebp+12) . Tout cela peut être fait dans l'interface graphique de Xcode :

  • cliquez sur "Run" dans le menu de l'application Xcode (en haut de l'écran)
  • sélectionnez "Débogueur".
  • dans la fenêtre du débogueur, cliquez sur "Show-Breakpoints".
  • cliquez sur la ligne "Enter Symbol-Name" et entrez "__CFXNotificationPost_old".
  • cliquez sur le "+" à l'extrême droite.
  • Sélectionnez "Debugger Command" dans la liste déroulante.
  • entrez "po {NSNotification *}($ebp+12)
  • (vous pouvez également activer la journalisation en cochant la case "Log" en bas)
  • Exécutez votre application dans une session de débogage du simulateur à partir de Xcode.

L'application arrêtera son exécution chaque fois qu'une NSNotification est postée et l'affichera dans la console gdb.

J'ai essayé de créer un tracepoint dans gdb mais j'ai échoué parce que les actions de tracepoint dans Xcode gdb semblent boguées - ou peut-être suis-je simplement trop stupide pour les faire fonctionner.

J'ai également essayé de créer un Instruments Dtrace script personnalisé, mais j'ai échoué car mon Karaté Dtrace n'est tout simplement pas assez fort.

Si vous parvenez à faire fonctionner l'une de ces dernières options, n'hésitez pas à la poster comme réponse alternative - je la voterai et la marquerai comme étant la réponse préférée.

UPDATE

Des années après cette question, j'ai trouvé le droite moyen de piéger toutes les notifications au niveau de CoreFoundation.

C'est ainsi que l'on peut procéder :

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 fait, j'ai un peu honte de ne pas avoir regardé de plus près l'interface de CoreFoundation avant.

21voto

Ben Gotow Points 7627

Hé Till, j'utilise beaucoup les notifications, et j'ai eu de sérieux problèmes pour les déboguer également. J'ai récemment publié une application appelée Spark Inspector ( http://sparkinspector.com/ ) qui rend le processus un peu plus facile. Vous ajoutez un framework à votre application, et il fait tourner NSNotificationCenter pour que vous puissiez voir un tableau de toutes les notifications envoyées et reçues dans votre application, avec les traces de pile où elles ont été envoyées et la liste de toutes les méthodes qui les ont observées. Je sais que c'est avec trois ans de retard, mais ça peut aider !

enter image description here

17voto

Mark Semsel Points 813

Je sais que la question posée est ancienne, mais je me suis dit que j'allais répondre avec quelques lignes de code.
Ce bloc vous permet de voir toutes les notifications affichées pendant l'exécution de votre application :

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

Ajoutez cela à la méthode viewWillAppear d'un contrôleur de vue approprié. (Bien entendu, vous devez supprimer cette méthode de votre code lorsque vous préparez votre application pour une quelconque distribution).

Assurez-vous également d'ajouter ceci :

[[NSNotificationCenter defaultCenter] removeObserver:self];

à la méthode viewWillDisappear correspondante du contrôleur de vue que vous avez sélectionné.

UPDATE : Même réponse, mais 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)
}

11voto

Senseful Points 11193

À des fins de débogage, je trouve qu'un point d'arrêt est en fait préférable à l'ajout de code au projet. Cependant, La solution de @Till n'a pas semblé fonctionner pour moi. J'ai trouvé une autre solution en ligne et l'a modifié un peu.

Point d'arrêt symbolique

  • Symbole : -[NSNotificationCenter postNotificationName:object:userInfo:]
  • Condition : ((NSRange)[$arg3 rangeOfString:@"^(_|NS|UI)" options:1024]).length == 0
  • Action : Commande du débogueur po $arg3
  • Continuer automatiquement après l'évaluation des actions

Notes :

  • La condition empêche toutes les notifications qui commencent par _ , NS o UI de s'afficher.
  • 1024 se réfère à NSRegularExpressionSearch qui ne semble pas être disponible pour ce point d'arrêt.
  • J'utilise .length == 0 au lieu de .location == NSNotFound parce que NSNotFound semble donner une valeur différente ( -1 o (NSUInteger)18446744073709551615 ) que la valeur retournée dans ce point d'arrêt ( 9223372036854775807 ).

1voto

Bem Points 737

Jusqu'à ce que la solution soit trouvée Swift 5.0 et Xcode 11 :

CFNotificationCenterAddObserver(
    CFNotificationCenterGetLocalCenter(),
    nil,
    { (notificationCenter, _, notificationName, _, dictionary) in
        print(notificationName)
        print(dictionary)
}, nil, nil, .deliverImmediately)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X