IOS释放⾃注销模式设计

达芬奇密码2018-08-23 09:14

作者:勾哲华 

⽇常开发中,我们经常会注册⼀些通知、发起⼀些请求,当我们不需要时应及时 注销通知,取消掉请求。否则,就有可能产⽣问题或者崩溃。⽐如我们会在控制器的 viewDidLoad⾥⾯注册⼀些通知,然后在dealloc⾥⾯注销掉通知。或者当我们退出控制 器时,将所有的当前发起的请求都Cancel掉。这在MRC开发下是⾮常常见的,因为请 求返回时,回调代理时可能为野指针。这种⼿动注销的⽅式有些繁琐,开发中经常会 遗忘导致问题被隐藏起来。正因为如此,我们希望可以提供⼀种架构去⾃动解决此类 注销通知、取消请求的调⽤。


分析这个场景可以发现,这些通知的观察者以及请求,它们的⽣命周期是依赖于 某个宿主的。当某个宿主对象销毁时,观察者以及请求也会被销毁。也就是⾃动调⽤ 注销的时机确定在对象释放时dealloc。同时,我们不应该对宿主的类型有特殊的要 求,宿主可能是控制器,某个⾃定义控件,或者数据模块,即宿主要求是NSObject类 型的对象。另外,我们希望可以⾃定义注销时的操作,⽐如是注销事件观察,或者是 取消请求。以上是对这个架构最基本的要求,此外我们还有⼀些更⾼的要求,希望架 构对性能的影响尽可能的⼩,避免产⽣⼀些循环引⽤的问题,使⽤要简单,最好有⼀ 些管理功能。


相对于其他需求点,捕获NSObject对象释放的时机是最为关键的需求。通常我们 会想到使⽤Method Swizzling的⽅式,替换NSObject的dealloc⽅法。但这⾥有⼏个问题 需要注意。 1、ARC开发下,dealloc作为关键字,编译器是有所限制的。会产⽣编译错误 “ARC forbids use of 'dealloc' in a @selector”。不过我们可以⽤运⾏时的⽅式进⾏解决。 


2、dealloc作为最为基础,调⽤次数最为频繁的⽅法之⼀。如对此⽅法进⾏替 换,⼀是代码的引⼊对⼯程影响范围太⼤,⼆是执⾏的代价较⼤。因为⼤多数dealloc 操作是不需要引⼊⾃动注销的,为了少数需求⽽对所有的执⾏都做修正是不适当的。


所以Method Swizzling可以作为最后的备选⽅案,但不适合作为⾸选⽅案。


另外⼀个思路是在宿主释放过程中嵌⼊我们⾃⼰的对象,使得宿主释放时顺带将 我们的对象⼀起释放掉,从⽽获取dealloc的时机点。显然AssociatedObject是我们想要 的⽅案。相⽐Method Swizzling⽅案,AssociatedObject⽅案的对⼯程的影响范围⼩,⽽ 且只有使⽤⾃动注销的对象才会产⽣代价。


鉴于以上对⽐,于是采⽤构建⼀个释放通知对象,通过AssociatedObject⽅式连接 到宿主对象,在宿主释放时进⾏回调,完成注销动作。

下⾯是释放通知类RFDestoryNotify的声明。通过rfDestoryNotifySetName⽅法对宿 主对象添加销毁监听,通过block回调具体的注销⽅法。


使⽤RFDestoryNotify之前需要对整个释放过程的时序特性有所了解,才能写出合 适的代码。所以,我们先编写如下的代码进⾏调试。

我们在宿主的释放函数dealloc(33),释放监听回调(58),宿主释放(61), 宿主释放后(62)添加四个回调。经过调试,其触发时序如下:

整个释放过程是同步的,先进⾏宿主s_owner的释放,再释放关联的 RFDestoryNotify对象,然后再触发回调RFDestoryNotifyBlock,完成整个过程。


从时序看,回调RFDestoryNotifyBlock在宿主s_owner的释放动作之后,但宿主 s_owner依然存活,由此可以知道宿主s_owner所持有的对象成员已全部执⾏过release, 但基本类型的成员值还保留着。


从调试结果看,实际也是这样。 因此,我们可以知道在释放回调时,宿主所持有的对象成员是不可靠的,但宿主 的内存地址是可靠的。这也是我们对RFDestoryNotify添加userInfo的原因。

以上是调试得到的结果,我们最好还是从OC的源代码层⾯加以确认。


上⾯是OC对象销毁时的处理,查找资料后,发现object_cxxDestruct负责遍历持有 的对象,并进⾏析构销毁_object_remove_assocations负责销毁关联对象的销毁。


clearDeallocating负责对weak持有本对象的引⽤置nil。从代码上看,代码执⾏时序与调 试时的时序也是⼀致的。这也证明了,上⾯描述的RFDestoryNotify的运⾏特性是正确 的,可靠的。 附: ARC 下dealloc过程及.cxx_destruct的探究 http://blog.sunnyxx.com/2014/04/02/objc_dig_arc_dealloc/


⽬前在我们的APP项⽬中,这项设计已经普遍得到应⽤。通过引⼊RFEvent,实 现了观察者释放时,可以⾃动注销相对应的事件观察。在⽹络事务请求Work中引⼊这 ⼀设计,可以声明全局、宿主两种不同的⽣命周期。⽅便对⽹络请求进⾏管理,增强 了程序的健壮性。在⾃定义dialog中引⼊这⼀设计,可以在回退时,⽅便的对宿主创建 的dialog进⾏销毁,为页⾯⾃动跳转提供便利。从实践效果看,对开发效率的提升和代 码质量的提升还是很明显的,因此推荐⼤家,希望对⼤家⽇后的开发⼯作起到积极的 作⽤。

网易云新用户大礼包:https://www.163yun.com/gift

本文来自网易实践者社区,经作者勾哲华授权发布。