引言
assign与weak有什么区别?面试的时候常常会被问到此问题,我们会回答weak修饰在对象释放时会自动变为nil,那么底层是怎么实现的呢?今天让我们来探讨一下,本文使用的runtime源码为大神提供的可编译版本objc4-706
storeWeak…
与weak相关的操作,应该都调用了次方法,稍后看一下这个方法的实现,首先做一下准备工作
我首先在项目中定义了两个类Person、Like实现如下
1 | @interface Person : NSObject |
其中main函数为
1 | void addLike(Person* person) |
准备工作已经就绪了,我们在Person的中weak修饰的属性下断点,并单步调试,会发现如下的函数调用栈
我们发现了关键的函数objc_storeWeak(...),下面我们要详细的探讨这一函数
1 | template <bool HaveOld, bool HaveNew, bool CrashIfDeallocating> |
在此方法中,我们可以看见一些关键点, SideTable, weak_unregister_no_lock(...), weak_register_no_lock(...),下面我们会逐一介绍
引用计数和弱引用依赖表 SideTable
SideTable 是一个结构体,主要用于管理对象的引用计数和 weak 表。在 NSObject.mm 中声明其数据结构
1 | struct SideTable { |
对于该结构体中的slock,refcnts,暂时我们不做讨论,我们主要讨论与弱引用相关的weak_table作用,其中weak_table_t的结构如下
1 | struct weak_table_t { |
这是一个全局弱引用表。使用不定类型对象的地址作为 key ,用weak_entry_t类型结构体对象作为value。其中的weak_entries成员,从字面意思上看,即为弱引用表入口。其实现也是这样的
1 |
|
让我们回到storeWeak(...)函数中,获取oldTable,与newTable,发现调用的函数为&SideTables()[newObj],&SideTables()[oldObj],其中该方法的实现为
1 | alignas(StripedMap<SideTable>) static uint8_t SideTableBuf[sizeof(StripedMap<SideTable>)]; |
1 | template<typename T> |
在上面我们可以看出StripedMap是一个模板类(Template Class),通过传入类(结构体)参数,会动态修改在该类中的一个 array 成员存储的元素类型,并且其中提供了一个针对于地址的 hash 算法,用作存储key。可以说, StripedMap 提供了一套拥有将地址作为 key 的 hash table 解决方案,而该方案采用了模板类,是拥有泛型性的,在这个类中有一个 array 成员,用来存储 PaddedT 对象,并且其中对于 [] 符的重载定义中,会返回这个 PaddedT 的 value 成员,这个 value 就是我们传入的 T 泛型成员,也就是 SideTable 对象。在 array 的下标中,这里使用了 indexForPointer 方法通过位运算计算下标,实现了静态的 Hash Table。而在 weak_table 中,其成员 weak_entry会将传入对象的地址加以封装起来,并且其中也有访问全局弱引用表的入口
weak_register_no_lock(…)
该方法将弱引用对象注册到弱引用表中,我们看一下它的具体实现
1 | id |
总结一下,我们weak对象的引用,与指向weak对象的引用,传入此函数中,假设是首次保存此weak对象的引用,构建出weak_entry_t的实例new_entry,判断当前的弱引用表是否需要增加长度,然后将new_entry插入到弱引用表weak_table中;如若不是第一次保存,首先取出旧的entry,并将指向weak对象的引用(即&(person.like))保存在entry中。
所以可以看出,一个entry中保存了所有指向该weak对象的弱引用
weak_unregister_no_lock(…)
1 | void |
其中该方法就是remove_referrer(entry, referrer);,在entry中移除了referrer,弱引用表中不再保存指向该weak对象的引用
weak对象释放
我们可以看见当一个被弱引用指向的对象释放时,会调用weak_clear_no_lock(...)方法
1 | void |
如方法中,通过referent,找到保存指向该对象的所有引用的实例entry,通过循环,将entry中的所有引用置为nil即可
weak引用表图解
参考资料
http://www.jianshu.com/p/ef6d9bf8fe59
http://kylinroc.github.io/objc-retain-release.html
http://ios.jobbole.com/89012/


