1. weak对象的实现函数
首先,看个例子(取自《Objective-C高级编程 iOS与OS X多线程和内存管理》的第一部分):
|
|
其模拟代码为
|
|
通过代码可以看到,weak指针(weak对象)的创建及释放,实际上是通过objc_initWeak() 和 objc_destroyWeak() 函数实现的。现在,就需要查看一下iOS的运行时系统是如何实现weak的功能的。
注:源代码采用objc.723版本
objc_initWeak的实现:
|
|
objc_destroyWeak的实现:
|
|
补充一下,修改weak对象的函数objc_storeWeak的实现:
|
|
可以看出,weak的相关功能,都是通过storeWeak函数进行实现的。通过不同的参数及条件设置实现weak对象的创建、修改和删除的。
2. storeWeak的实现
还是不废话,直接贴出实现源码(删除了部分无关代码):
|
|
在以上代码中可以看到,我们所有的操作(注册weak、移除weak等)都是发生在SideTable实例中的。且在操作时需要在有锁的条件下执行。我们看一下SideTable到底是何方神圣:
|
|
可以看到,SideTable即为内存管理的精髓。其内部包含的就是引用计数表和weak弱引用表。
SideTable是全局对象(不支持析构,会crash),是由分离锁进行管理的StripedMap对象:
|
|
其中,SideTables函数的实现为:
|
|
在StripedMap模板类中,我们可以找到获取SideTable的相关实现:
|
|
其中,public方法中的实现即说明了一切:通过对象地址得到的hash值,取出数组中保存的值(这里即为SideTable对象)。且通过hash算法还可知道,一个StripedMap对象在64位系统下包含64个子单元。可以理解为一个分离锁管理着64个对象的SideTable对象。
由于本篇学习的是weak的实现,故我们暂时只讨论weak_table_t这个结构(这个还凑合,引用计数还根本不懂…)。
3. weak弱引用表
首先看一下weak_table_t的数据结构:
|
|
weak_table_t内部保存着由week_entry_t实例组成的数组。
内部的weak_entry_t即为真正的键值对:key为引用的对象,value为weak指针组成的数组。
|
|
weak_entry_t结构体固定为40字节大小。
其中,为了快速访问weak指针,weak_entry_t中直接设置了一个包含4个元素的静态数组。当指向对象的weak指针超过上限时,就使用动态数组进行存储(将静态数组中的weak指针copy到动态数组中,之后再存入新的weak指针)。
weak_entry_t提供给外部用于识别自身正在使用哪种存储数组的函数。
有了以上这些知识,我们就可以解释SideTable中的weak表是如何注册weak对象并如何清除weak对象的过程了。
4. 在weak引用表中注册weak对象
|
|
其中,通过原始对象,在weak表中查找对应entry的过程如下:
|
|
注:
在查找遍历索引begin时,系统使用的方式是通过对象的hash值和weak_table的mask值进行按位与运算。
其中mask是num_entries-1,即如果此弱引用表的weak_entries个数是4,mask即为3,也就是0b11。
对象地址hash之后,通过mask进行按位与运算得到的,就只有mask值对应的范围,即0b00~0b11,故index取值范围是0~3。系统巧妙地将对象地址转化为索引,在数组中查找对应位置的数据。
后面使用while循环的原因是,由于hash值可能会重复,得到的index位置可能已经存在其他数据,故对index进行偏移处理,待新位置的元素符合要求,再进行操作。其中hash_displacement就是标记偏移量的。如果超过最大偏移量max_hash_displacement,则数组中没有符合要求的位置索引。
向已经存在的weak_entry_t中插入新的weak指针,操作如下:
|
|
而对于weak_table_t弱引用表中,没有引用对象指向的weak_entry_t对象时,直接创建新entry,将weak指针写入后,直接将此entry加入到弱引用表中。此过程与查询entry的过程非常相似:
|
|
5. 在weak弱引用表中移除weak对象
相对的,当指向的对象将要释放时,运行时系统会将注册到weak表中的原始对象相关的所有weak指针信息移除。也就实现了weak自动置为nil的功能。
|
|
整体过程与注册weak指针的流程非常相似。查询到weak_entry_t对象后,移除内部的weak指针,最后清除此entry对象即可。
移除entry中指定的weak指针的过程如下(与插入weak指针非常相似):
|
|
在weak_table_t中移除weak_entry_t对象就比较简单了:
|
|
这里,weak_compact_maybe函数会检查weak表中entry的存储占用率,根据实际情况释放相应空间。
不仅如此,weak_grow_maybe函数也如此,会根据占用率动态扩大存储空间。其内部实现都是通过weak_resize函数实现的。
|
|
这里,我们就知道了,weak_table_t中的weak_entries成员实际上是weak_entry_t组成的数组(calloc创建的连续内存空间)。
6. 总结
至此,我们基本了解了创建和释放一个weak弱指针对象所需要做的主要工作。也明白了weak指针在原对象释放后自动置为nil的实现方式。
此外,我们还了解了:
- 苹果对于将hash值转化为数组索引index的转换方式及冲突后的索引偏移的操作。
- 对于weak_entry_t数据结构中,存储weak指针的两种方式(快速量少用静态数组,量大使用动态数组的可伸缩方式)的分段式存储思想。