在iOS中,使用weak关键字能够对内存对象进行弱引用,基于这个特性,使用weak关键字能够解决许多问题,例如delegate中对象的循环持有问题、Block对对象的强引用导致的对象无法及时释放问题。
为何weak关键字能够实现对内存对象的弱引用,今天我们就来探究一下。
首先在分析weak关键字实现原理之前,先介绍一下相关的数据结构,这些数据结构其中一部分可能在其他地方有所提及,但本文只列出与weak关键字有关的一部分。
这些数据结构全部存在于runtime源码中,相关内容可以在 objc-weak
文件中查看。
一、数据结构
1. SideTables
SideTables
本质上是一个全局的 StripedMap
。
StripedMap
本质是一个数组,且在iOS系统下,容量为64。
该数据结构通过实现[]
操作,实现了类似字典的功能:可通过传入一个对象作为key值,来获取对应的Item。
在 SideTables
中, Item类型为 SideTable
,由此可见,对于任何一个对象, SideTables
都能根据其地址对应到具体的一个 SideTable
上。
2. SideTable
SideTable
中包含三个元素,分别是 1.自旋锁 2.记录对象引用计数的字典 3.记录对象弱引用信息的数据结构 weak_table_t
。
其中 weak_table_t
是与weak关键字有关的数据结构,其余二者暂可不用关注。
3. weak_table_t
weak_table_t
本质上是一个数组,其中每个Item为 weak_entry_t
。
4. weak_entry_t
weak_entry_t
就比较有意思了,它本质上是个字典。
其中的key值为对象,而value对应为一个数组,该数组最初为内部的一个大小为4的数组,当数组大小超过4后,则变为内部一个可变大小数组。
无论value值对应的数组是固定大小还是可变大小,数组中保存的值均为 weak_referrer_t
类型的数据。
5. weak_referrer_t
weak_referrer_t
本质上是 objc_object **
,即Objective-C对象的地址。
所以,weak_entry_t
value数组中,每一个Item均为一个地址,即weak对象的地址。
以上就是weak实现原理中所涉及到的所有数据结构,具体关系如下图:
二、weak_table_t
与 weak_entry_t
相关方法
在正式探究weak关键字实现原理之前,先来看一些操作 weak_table_t
与 weak_entry_t
的方法。
1. 从 weak_table_t
中查询对应的 weak_entry_t
static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
//获取weak_table_t的数组结构
weak_entry_t *weak_entries = weak_table->weak_entries;
if (!weak_entries) return nil;
//获取对象地址,并根据地址映射到数组结构长度内,得到对应下标
size_t index = hash_pointer(referent) & weak_table->mask;
//线性探寻数组结构中对应的value所在index
size_t hash_displacement = 0;
while (weak_table->weak_entries[index].referent != referent) {
index = (index+1) & weak_table->mask;
hash_displacement++;
if (hash_displacement > weak_table->max_hash_displacement) {
return nil;
}
}
//返回查询到的weak_entry_t
return &weak_table->weak_entries[index];
}
2. 向 weak_table_t
中增加新的 weak_entry_t
static void
weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
{
//获取weak_table_t的数组结构
weak_entry_t *weak_entries = weak_table->weak_entries;
assert(weak_entries != nil);
//获取对象地址,并根据地址映射到数组结构长度内,得到对应下标
size_t index = hash_pointer(new_entry->referent) & (weak_table->mask);
//线性探寻数组结构中value所应在的位置
size_t hash_displacement = 0;
while (weak_entries[index].referent != nil) {
index = (index+1) & weak_table->mask;
hash_displacement++;
}
//将```weak_entry_t```放入```weak_table_t```对应位置,并更新相关数据
weak_entries[index] = *new_entry;
weak_table->num_entries++;
if (hash_displacement > weak_table->max_hash_displacement) {
weak_table->max_hash_displacement = hash_displacement;
}
}
3. 扩展 weak_table_t
容积
weak_entry_insert
方法不需要考虑 weak_table_t
容积,因为runtime代码中在调用 weak_entry_insert
方法前都会调用 weak_grow_maybe
方法来在必要的时候扩展 weak_table_t
容积。
static void
weak_grow_maybe(weak_table_t *weak_table)
{
size_t old_size = TABLE_SIZE(weak_table);
//当weak_table_t容积超过3/4时,进行容积扩展
if (w