【问题标题】:NSNotificationCenter trapping and tracing all NSNotificationsNSNotificationCenter 捕获和跟踪所有 NSNotifications
【发布时间】:2011-04-13 02:51:41
【问题描述】:

为了更好地了解“幕后”发生的事情,我很乐意对我的应用程序中发生的任何通知进行完整跟踪。

虽然我很天真,但我尝试的第一件事是这样注册:

在我的应用程序中的某处:

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

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

我确实通过这种方式收到了许多通知。但在某些时候,应用程序确实崩溃了。堆栈跟踪显示它在 implementationClass 中因 EXC_BAD_ACCESS 而崩溃,根据我的经验,这确实表明某些东西在其释放后被调用。然而我的观察对象仍然活着,它的释放器还没有被调用(还)。

接下来我尝试设置一个指向-[NSNotificationCenter postNotification:] 的断点,然后每当我的断点被捕获时在gdb-console 中运行po {NSNotification *}($ebp+16)。这确实揭示了一些通知,但不是我所期待/希望的全部。例如,我的应用程序确实正确处理了方向更改,但在重新定向设备时(在模拟器中)我没有看到任何通知被捕获。

我错过了什么? 是否有可靠地观察 NSNotificationCenter 的方法(例如工具)?

感谢任何提示。

【问题讨论】:

  • 走得更远了——断点方法似乎指向了正确的方向......我依赖于系统正在使用objective-c方法调用这一事实,它并不适用于所有人在使用过的通知中,我必须在某个时候捕获 CFNotificationCenter ......将继续尝试......
  • 自发布此问题以来,Spark Inspector sparkinspector.com 已发布,它附带了一个用于监控(和重新发送)NSNotifications 的工具。

标签: iphone trace nsnotifications


【解决方案1】:

Swift 5.0 和 Xcode 11 中的@Till 解决方案:

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

【讨论】:

    【解决方案2】:

    出于调试目的,我发现断点实际上比在项目中添加代码更好。但是,@Till's solution 似乎对我不起作用。我找到了another solution online,并对其进行了一些调整。

    符号断点

    • 符号-[NSNotificationCenter postNotificationName:object:userInfo:]
    • 条件((NSRange)[$arg3 rangeOfString:@"^(_|NS|UI)" options:1024]).length == 0
    • 操作:调试器命令po $arg3
    • 评估操作后自动继续

    注意事项:

    • 条件是阻止显示以_NSUI 开头的任何通知。
    • 1024 指的是 NSRegularExpressionSearch,它似乎不适用于该断点。
    • 我使用.length == 0 而不是.location == NSNotFound,因为NSNotFound 的计算结果似乎与此断点中的返回值(9223372036854775807)不同。

    【讨论】:

    • 此解决方案应高于此处建议的其他解决方案;它很干净,使用内置的 Xcode 工具,不需要添加/更改代码。
    • 要在我的 watchOS 6.1.1 模拟器上使用它,我必须将操作更改为 po *(void **)($esp+12),否则我会看到此错误:use of undeclared identifier '$arg3'
    【解决方案3】:

    我知道发布的问题很老,但我想我会用几行代码来回应。
    您可以通过此块查看应用运行时发布的所有通知:

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

    将其添加到适当视图控制器的 viewWillAppear 方法中。 (当然,在为任何类型的分发准备应用程序时,您应该从代码中删除它。)

    另外,请务必添加:

    [[NSNotificationCenter defaultCenter] removeObserver:self];
    

    到你选择的视图控制器对应的viewWillDisappear方法。

    更新: 相同的答案,但在 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)
    }
    

    【讨论】:

      【解决方案4】:

      Hey Till——我经常使用通知,但在调试它们时也遇到了一些严重的问题。我最近发布了一个名为 Spark Inspector (http://sparkinspector.com/) 的应用程序,它使这个过程更容易一些。你向你的应用程序添加一个框架,它会调动 NSNotificationCenter,这样你就可以看到在我们的应用程序中发送和接收的所有通知的表格,其中包含发送它们的堆栈跟踪以及观察它们的所有方法的列表。我知道这大约晚了三年,但它可能会有所帮助!

      【讨论】:

      • 哇,你的视图控制器的 3d 视图太疯狂了
      【解决方案5】:

      我得到的唯一解决方案是使用断点。

      我在__CFXNotificationPost_old (CoreFoundation) 添加了一个断点,并将其与调试器命令po {NSNotification *}($ebp+12) 捆绑在一起。所有这些都可以在 Xcode GUI 中很好地实现:

      • 单击 Xcode 应用程序菜单(屏幕顶部)上的“运行”
      • 选择“调试器”
      • 在“调试器”窗口中单击“显示断点”
      • 点击“输入符号名称”行并输入“__CFXNotificationPost_old”
      • 点击最右侧的“+”
      • 在该下拉列表中选择“调试器命令”
      • 输入“po {NSNotification *}($ebp+12)
      • (您可能还想通过选中底部的“日志”复选框来激活日志记录)
      • 在 Xcode 中的模拟器调试会话中运行您的应用程序

      每当发布 NSNotification 并在 gdb-console 中显示它时,应用程序都会停止执行。

      我确实尝试在 gdb 中创建跟踪点,但失败了,因为 Xcode gdb 中的跟踪点操作似乎有问题 - 或者我太笨了,无法让它们正常工作。

      我还尝试创建自定义 Instruments Dtrace 脚本,但失败了,因为我的 Dtrace 空手道不够强。

      如果您设法使后面的任何选项起作用,请继续发布它们作为替代答案 - 我会投票并将它们标记为最喜欢的选项。

      更新

      在这个问题之后,我找到了正确在 CoreFoundation 级别捕获所有通知的方法。

      这是可以做到的:

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

      其实之前没有仔细看CoreFoundation的界面,我有点惭愧。

      【讨论】:

      • 您好,Till,感谢您的这篇文章,它确实帮助我更深入地了解 gdb 和断点。但是还有一个问题:你怎么知道 ebp 的偏移量来向你显示通知的指针(实际上我想为注册通知做同样的事情)。
      • 顺便说一句,如果您在播放符号右下方设置复选标记,则断点的行为就像跟踪...
      • 真的很高兴您在其中找到了价值。我通过玩一些游戏并观察结果找到了偏移量 - 也通过了解从我黑暗的过去中进行大量调试和逆转会话的期望;o)
      • 目前这在 Swift 中不起作用,因为 CFNotificationCenterAddObserver 参数并不都存在于 Swift 中
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-11-19
      • 2012-08-24
      • 2014-09-12
      • 2012-03-02
      • 2012-02-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多