关于 ObjC 通知的一个神奇崩溃

关于 ObjC 通知的一个神奇崩溃

💡 原文中文,约2600字,阅读约需7分钟。
📝

内容提要

近日发现一个发通知时触发的 EXC_BAD_ACCESS 崩溃,经过排查发现是订阅端设置的参数类型不匹配导致的。将参数类型改为 Notification 后问题解决。同时发现该崩溃受 Xcode 缓存影响,且复现路径较苛刻。对于为什么纯 Swift 不会崩溃而混编时会崩溃以及两种参数导致的崩溃类型不一样的问题,仍需继续探索答案。

🔎

延伸解读

崩溃复现的三个必要条件

文章指出,该崩溃的复现需要同时满足三个条件:ObjC 与 Swift 混编、Swift 订阅端将参数设置为可选闭包类型且默认值为 nil、由 ObjC 端发送通知。缺少任一条件都不会触发崩溃,这解释了为何在纯 Swift 环境或简单 demo 中难以复现,也提醒开发者在排查类似问题时需关注混编边界和参数签名。

Xcode 缓存对崩溃复现的干扰

作者发现,即使将发送端改回 Swift,崩溃仍会因 Xcode 缓存而持续出现,只有 clean 后重新运行才恢复正常。这说明缓存可能保留了旧的编译产物或方法签名信息,导致崩溃行为不稳定。在验证修复或复现问题时,应主动清理缓存,避免被残留状态误导。

不同参数类型导致崩溃信息差异

当订阅端参数误设为闭包类型时,崩溃表现为 EXC_BAD_ACCESS;而误设为 String 等类型时,则报 unrecognized selector sent to instance。作者推测这与 Xcode 生成的 ObjC Header 中方法指针命名有关:闭包类型对应 onReceiveWithOther:,ObjC 发送通知时找不到 onReceive 指针,从而引发不同崩溃路径。

❓

Q&A

ObjC 发通知时出现 EXC_BAD_ACCESS 崩溃,可能是什么原因?

可能是订阅端(Swift 侧)设置的通知回调方法参数类型不匹配。例如参数被声明为可选闭包类型 `(() -> Void)?` 并带默认值 nil,而 ObjC 发送通知时实际调用的是 `onReceiveWithOther:` 方法,导致访问不存在的指针而崩溃。

如何解决 ObjC 通知导致的 EXC_BAD_ACCESS 崩溃?

将订阅端回调方法的参数类型改为 `Notification`(或 `Notification?`),并确保 selector 正确指向带参数的方法,例如 `@objc func onReceive(_ sender: Notification?)`,同时注册时使用 `#selector(self.onReceive(_:))`。

为什么这个通知崩溃在纯 Swift 项目中不会出现,只在混编时出现?

文章指出纯 Swift 发送通知时不会崩溃,而混编(ObjC 发送、Swift 订阅)时会崩溃。具体原因尚待探索,作者猜测可能与 Xcode 生成的 ObjC Header(如 Demo-Swift.h)中方法签名有关:ObjC 端只看到 `onReceiveWithOther:` 方法,没有 `onReceive` 方法指针。

这个崩溃的复现条件有哪些?

复现需要同时满足:1) ObjC 和 Swift 混编;2) Swift 订阅通知,回调参数设置为可选的闭包类型且默认值为 nil;3) 由 ObjC 端发送通知。此外,崩溃还受 Xcode 缓存影响,修改代码后可能需要 clean 才能复现或消除。

为什么参数类型不匹配时,闭包类型导致 EXC_BAD_ACCESS,而其他类型导致 unrecognized selector?

文章提到参数类型不匹配导致崩溃符合预期,但崩溃类型不同:闭包类型崩溃为 EXC_BAD_ACCESS,其他类型(如 String)崩溃为 `unrecognized selector sent to instance`。具体原因作者尚未明确,仍需继续探索。

崩溃信息指向 AppDelegate 或发通知代码行,为什么说它具有误导性?

因为崩溃发生在发通知时,但根本原因是订阅端参数类型不匹配,崩溃信息可能指向发通知的代码行或 AppDelegate,容易让人误以为是发送端的问题。实际排查需关注订阅端的方法签名。

🏷️

标签

➡️

继续阅读