NSNotificationCenter trapping and tracing all NSNotifications
Asked Answered
W

5

35

For some better understanding on what happens “under the hood”, I would love to do a complete trace of any notifications happening within my application.

Naïve as I am, the first thing I tried was registering like this:

Somewhere in my app:

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

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

I do actually get a number of notifications that way. But at some point the application does crash. The stack trace shows it is crashing with EXC_BAD_ACCESS in realizeClass, which from my experience does indicate that something gets called after its deallocation. My observing object however still is alive, its deallocator has not been called (yet).

Next thing I tried was setting a breakpoint towards -[NSNotificationCenter postNotification:] and then run po {NSNotification *}($ebp+16) inside the gdb-console whenever my breakpoint is trapped. That did reveal a few notifications but not all that I am expecting/hoping for. For example, my application does handle orientation-changes properly but I do not see any notifications being trapped when reorienting the device (in the simulator).

What am I missing? Is there a way (e.g. a tool) for reliably observing a NSNotificationCenter?

Thanks for any hint.

Warfield answered 16/9, 2010 at 9:11 Comment(2)
got a little further - the breakpoint-method seems to lead to the right direction... I was relying on the fact that the system was using objective-c method-calls which it is not for all of the used notifications, I have to trap CFNotificationCenter at some point... will keep on trying....Warfield
Since this question was posted, the Spark Inspector sparkinspector.com has been released, and it comes with a tool for monitoring (and resending) NSNotifications.Tonguelash
P
13

For debugging purposes, I find that a breakpoint is actually better than adding code to the project. However, @Till's solution didn't seem to work for me. I found another solution online, and tweaked it a bit.

Symbolic Breakpoint

  • Symbol: -[NSNotificationCenter postNotificationName:object:userInfo:]
  • Condition: ((NSRange)[$arg3 rangeOfString:@"^(_|NS|UI)" options:1024]).length == 0
  • Action: Debugger Command po $arg3
  • Automatically continue after evaluating actions

Notes:

  • The condition is preventing any notifications that begin with _, NS, or UI from being displayed.
  • 1024 refers to NSRegularExpressionSearch, which doesn't seem to be available for that breakpoint.
  • I use .length == 0 instead of .location == NSNotFound because NSNotFound seems to evaluate to a different value (-1 or (NSUInteger)18446744073709551615) than the returned value in this breakpoint (9223372036854775807).
Pythagoras answered 25/10, 2016 at 5:16 Comment(2)
This solution should be elevated above the others suggested here; it is clean, using built-in Xcode facilities and does not require code additions/changes.Kaila
To use this on my watchOS 6.1.1 simulator, I had to change the action to po *(void **)($esp+12), otherwise I see this error: use of undeclared identifier '$arg3'Myrtlemyrvyn
W
60

The only solution I got to work was using breakpoints.

I added a breakpoint at __CFXNotificationPost_old (CoreFoundation) and bundled that with a Debugger Command po {NSNotification *}($ebp+12). All of this is nicely doable within the Xcode GUI:

  • click on "Run" on the Xcode application menu (top of the screen)
  • select "Debugger"
  • within the Debugger window click on "Show-Breakpoints"
  • click on the "Enter Symbol-Name"-line and enter "__CFXNotificationPost_old"
  • click on the "+" on the very right side
  • select "Debugger Command" on that dropdown-list
  • enter "po {NSNotification *}($ebp+12)
  • (you may also want to activate logging by checking the "Log" checkbox at the bottom)
  • run your app in a simulator-debug-session from within Xcode

The app will stop its execution whenever a NSNotification is posted and display it within the gdb-console.

I did try to create a tracepoint within gdb but failed because the tracepoint actions within Xcode gdb seem to buggy - or maybe I am just too stoopid to get them working.

I also tried to create a custom Instruments Dtrace script, but failed as my Dtrace Karate just isnt strong enough.

If you manage to get any of the latter options to work, please go ahead and post them as an alternative answer - I will upvote and mark them as the favored one.

UPDATE

Ages after this question, I found the right way of trapping all notifications on the CoreFoundation level.

This is how it can be done:

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

I actually feel a little ashamed that I did not look closer at CoreFoundation's interface before.

Warfield answered 17/9, 2010 at 19:31 Comment(5)
Hi Till, thanks for this post, it really helped me in getting deeper into gdb and breakpoints. But one question remains: How did you know the offset of ebp to show you the pointer to the notification (actually I want to do the same for register the notifications).Escuage
Hah, one click later I found it here: clarkcox.com/blog/2009/02/04/inspecting-obj-c-parameters-in-gdbEscuage
btw, if you set the checkmark on the right below the play symbol the breakpoint behaves like a trace...Escuage
Really glad you found value in this. The offsets I found by playing around a little and observing the results - also by knowing what to expect from loads of debugging and reversing sessions from my dark past ;o)Warfield
As of now this doesn't work in Swift as the CFNotificationCenterAddObserver parameters don't all exist in SwiftEyewitness
T
21

Hey Till—I use notifications a lot, and I had some serious problems debugging them as well. I recently released an app called the Spark Inspector (http://sparkinspector.com/) that makes the process a bit easier. You add a framework to your app, and it swizzles NSNotificationCenter so you can see a table of all of the notifications sent and received in y our app, with the stack traces where they were sent and the list of all the methods that observed them. I know it's about three years late, but it may help!

enter image description here

Tonguelash answered 15/8, 2013 at 5:31 Comment(1)
woah, that 3d view of your view controller is insaneChowchow
P
17

I know the posted question is old, but I figured I'd respond with a couple lines of code.
You can see all the notifications posted while your app is running via this block:

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

Add that to the viewWillAppear method of an appropriate view controller. (Of course, you should remove this from your code when preparing your app for any kind of distribution.)

Also, be sure to add this:

[[NSNotificationCenter defaultCenter] removeObserver:self];

To your selected view controller's corresponding viewWillDisappear method.

UPDATE: Same answer, but in 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)
}
Peyton answered 4/5, 2015 at 17:46 Comment(0)
P
13

For debugging purposes, I find that a breakpoint is actually better than adding code to the project. However, @Till's solution didn't seem to work for me. I found another solution online, and tweaked it a bit.

Symbolic Breakpoint

  • Symbol: -[NSNotificationCenter postNotificationName:object:userInfo:]
  • Condition: ((NSRange)[$arg3 rangeOfString:@"^(_|NS|UI)" options:1024]).length == 0
  • Action: Debugger Command po $arg3
  • Automatically continue after evaluating actions

Notes:

  • The condition is preventing any notifications that begin with _, NS, or UI from being displayed.
  • 1024 refers to NSRegularExpressionSearch, which doesn't seem to be available for that breakpoint.
  • I use .length == 0 instead of .location == NSNotFound because NSNotFound seems to evaluate to a different value (-1 or (NSUInteger)18446744073709551615) than the returned value in this breakpoint (9223372036854775807).
Pythagoras answered 25/10, 2016 at 5:16 Comment(2)
This solution should be elevated above the others suggested here; it is clean, using built-in Xcode facilities and does not require code additions/changes.Kaila
To use this on my watchOS 6.1.1 simulator, I had to change the action to po *(void **)($esp+12), otherwise I see this error: use of undeclared identifier '$arg3'Myrtlemyrvyn
D
1

@Till solution in Swift 5.0 & Xcode 11:

CFNotificationCenterAddObserver(
    CFNotificationCenterGetLocalCenter(),
    nil,
    { (notificationCenter, _, notificationName, _, dictionary) in
        print(notificationName)
        print(dictionary)
}, nil, nil, .deliverImmediately)
Disesteem answered 19/10, 2019 at 14:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.