NSNotificationCenter Crash 防护方案

背景介绍

最近梳理以前项目中的一些问题解决方案,发现网上 关于 NSNotificationCenter 导致的 EXC_BAD_ACCESS 较少,遂将之前本人想出来的方案分享出来。

NSNotificationCenter 想必大家都很熟悉了,iOS 9 之前,当我们使用 - (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject; 的时候,NSNotificationCenter 并不会对 observer 进行强应用,iOS 9 之前使用的是unsafe_unretained 引用,因此当我们的对象 dealloc 的时候,一般是需要进行 removeObserver 的操作,然而由于一些开发同学不清楚这个规则或者是开发时间紧张,忘记了在 dealloc 的时候释放。那么熟悉的 EXC_BAD_ACCESS crash 就极有可能会出现在我们 App 身上了。
关于 NSNotificationCenter 导致的这个 Crash, 网络上有一些文章会给出解决方案,通常是 Hook 对象的 dealloc 方法,然后在 dealloc 中 removeObserver。
但是 Hook 所有对象的 dealloc 方法通常会造成不必要的性能损耗。今天本人提供一种自己思考出来的解决方法,虽然现在支持 iOS 9 以下的项目已经很少了。但是这个解决问题的思路还是很重要的,也许可以应用在后面类似的问题上。

解决方法

avatar
笔者本次要分享的方法就在这张图上了。
对象在 dealloc 的时候会去释放它的关联对象,那么我们就可以 Hook NSNotificationCenter 的 addObserver:selector:name:object: 方法。
假设原本的 observer 是 obj, 那么我们为这个 obj 设置一个 associatedObj,
AssociationsManager 强引用 associatedObj, associatedObj 弱引用 obj, 然后将 associatedObj 作为 observer 添加进 NSNotificationCenter。后面 associatedObj 收到 notification 的时候,将 selector 调用转发给 obj,转发的相关代码可以使用 YYWeakProxy

obj 在 dealloc 的时候,会调用 _object_remove_assocations 移除关联对象,关联对象因为没有被其它对象强应用,所以会调用自己的 dealloc 方法,此时我们进行
[[NSNotificationCenter defaultCenter] removeObserver:self];
问题解决!!!

因为项目不再支持 iOS 8,代码已经被删掉了,需要翻一下项目的 git 记录,今天先将思路分享出来。待笔者重新整理一下,后面放一份代码放在 Github 上。

其他思考

代码规范
1、在项目的Build Phases 或者 git commit 的时候加一些钩子操作,使用脚本+正则表达式进行代码扫描,如果只有 add 没有对应的 remove 的操作的时候,中断编译操作或者 git commoit 操作。
2、编写 Clang 插件,遍历语法树,如果有 add 操作,就自动插入 remove 操作或者生成代码分析报告,在报告中指出问题。