提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
weak是一种防止循环引用,也能在对象回收时自动置为nil的引用,下面从源码角度来分析weak的实现原理。
一、SideTable介绍
SideTable是用于存储扩展的引用计数和弱引用集合的结构体,SideTable的结构体:
- slock 自旋锁,用于上锁/解锁 SideTable。
- refcnts 参考第三节
- weak_table 存储对象弱引用指针的hash表。
1.1 weak_table_t(objc_weak.h)
weak_table_t结构体如图
- weak_entries: hash数组,用来存储弱引用对象的相关信息weak_entry_t
- num_entries: hash数组中的元素个数
- mask:hash数组长度-1,会参与hash计算。(注意,这里是hash数组的长度,而不是元素个数。比如,数组长度可能是64,而元素个数仅存了2个)
- max_hash_displacement:可能会发生的hash冲突的最大次数,方便hashtable扩展。
1.2 weak_entry_t(objc_weak.h)
weak_entry_t也是一个hash结构,占位16 * 4,key是对象地址,value是弱引用指针集合。通过操作指针的指针,就可以使得weak 引用的指针在对象析构后,指向nil。weak_entry_t结构体如图所示。
从图中可以看出weak_entry_t是个共用体,占用4个字节。在联合体的内部有定长数组inline_referrers[4]和动态数组*referrers两种方式来存储弱引用对象的指针地址。通过out_of_line()这样一个函数方法来判断采用哪种存储方式。当弱引用该对象的指针数目小于等于4时,使用定长数组。当超过4时,会将定长数组中的元素转移到动态数组中,并之后都是用动态数组存储。
二、weak引用本质
weak初始化方法调用objc_initWeak(NSObjct.mm),代码如下:
- location是_weak指针的指针,后续方便置为nil。
- newObj是对象的指针。
weak引用destroy调用objc_destroyWeak,代码如下:
- location是_weak指针的指针,后续方便置为nil。
2.1 weak指针的入口storeWeak
这里删除多线程操作和初始化检查代码,简化后代码如下:
storeWeak接收的5个参数:
参数名 | 备注 |
---|---|
haveOld | weak指针之前是否指向了一个弱引用 |
haveNew | weak指针是否需要指向一个新的引用 |
crashIfDeallocating | 如果被弱引用的对象正在析构,此时再弱引用该对象是否应该crash。 |
location | weak指针的指针 |
newObj | 对象的指针 |
由上图可以看出:
- 如果weak指针之前指向了一个弱引用,则会调用weak_unregister_no_lock 方法将旧的weak指针地址移除。
- 如果weak指针需要指向一个新的引用,则会调用weak_register_no_lock 方法将新的weak指针地址添加到弱引用表中。
- 使用setWeaklyReferenced_nolock将isa中weakly_referenced位置1,表示对象由weak引用。
三、weak引用创建(objc-weak.mm)
weak引用的创建入口是weak_register_no_lock,这里去除了判nil和一些异常判断的代码,具体代码如下:
weak_register_no_lock接收的4个参数:
参数名 | 备注 |
---|---|
*weak_table | weak_table_t 结构类型的全局的弱引用表。 |
referent_id | 对象指针 |
*referrer_id | weak指针的指针 |
crashIfDeallocating | 如果被弱引用的对象正在析构,此时再弱引用该对象是否应该crash。 |
由上图可以看出:
- 用对象指针得到weak_entry,有的话将weak指针的指针直接存入,没有的话创建weak_entry,再插入weak指针的指针。
3.1 插入weak_entry(append_referrer)
对append_referrer方法注解如下:
四、weak引用移除(objc-weak.mm)
weak引用的移除入口是weak_unregister_no_lock,具体代码如下:
weak_unregister_no_lock接收的3个参数:
参数名 | 备注 |
---|---|
*weak_table | weak_table_t 结构类型的全局的弱引用表。 |
referent_id | 对象指针 |
*referrer_id | weak指针的指针 |
4.1 删除weak(remove_referrer)
对remove_referrer方法代码注解如下:
五、dealloc对weak的清除
dealloc的前序调用参考2节。dealloc对weak的清除入口clearDeallocating方法(objc-object.h)如下:
对clearDeallocating_slow的方法(NSObject.mm)调用如下:
由上图可知真正删除weak引用是调用weak_clear_no_lock这个方法,代码注解如下: