2019-12-19
一、概述
Weak 指针是为了解决引用计数内存管理可能存在的循环引用问题而设计的,一种指向对象而不增加对象的引用计数,且指向的对象析构时能自动置nil
的特殊指针。
在 NSObject.mm 中找到实现 weak 指针的关键代码template <bool HaveOld, bool HaveNew, bool CrashIfDeallocating> static id storeWeak(id *location, objc_object *newObj)
,该方法用于将对象赋值给__weak
类型引用。
二、源码分析
2.1 准备工作
首先了解几种数据结构,不需完全理解其原理,知道其功能即可。
2.1.1 DisguisedPtr类
DisguisedPtr
类的定义如下。它对一个指针伪装处理,保存时装箱,调用时拆箱。可不纠结于为什么要将指针伪装,只需要知道DisguisedPtr<T>
功能上等价于T*
即可。
template <typename T>
class DisguisedPtr {
uintptr_t value;
static uintptr_t disguise(T* ptr) {
return -(uintptr_t)ptr;
}
static T* undisguise(uintptr_t val) {
return (T*)-val;
}
public:
DisguisedPtr() { }
DisguisedPtr(T* ptr)
: value(disguise(ptr)) { }
DisguisedPtr(const DisguisedPtr<T>& ptr)
: value(ptr.value) { }
DisguisedPtr<T>& operator = (T* rhs) {
value = disguise(rhs);
return *this;
}
DisguisedPtr<T>& operator = (const DisguisedPtr<T>& rhs) {
value = rhs.value;
return *this;
}
operator T* () const {
return undisguise(value);
}
T* operator -> () const {
return undisguise(value);
}
T& operator * () const {
return *undisguise(value);
}
T& operator [] (size_t i) const {
return undisguise(value)[I];
}
};
2.1.2 weak_entry_t结构体
首先了解weak_entry_t
结构体,代码如下。
#if __LP64__
#define PTR_MINUS_1 63
#else
#define PTR_MINUS_1 31
#endif
#define WEAK_INLINE_COUNT 4
typedef objc_object ** weak_referrer_t;
struct weak_entry_t {
DisguisedPtr<objc_object> referent;
union {
struct {
weak_referrer_t *referrers;
uintptr_t out_of_line : 1;
uintptr_t num_refs : PTR_MINUS_1;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
};
其中weak_referrer_t
类型是 对象的引用的地址,是指针的指针。weak_entry_t
结构体的结构如下:
referent
:对象的引用,指向 被弱引用的对象;- 联合体保存了 对象(
*referent
)的所有弱引用的地址。有两种保存形式:- inline 形式:
out_of_line
标记为0,限定referrers
长度为WEAK_INLINE_COUNT
,inline 形式用数组数据结构 保存对象(*referent
)的所有弱引用的地址; - out of line形式:
out_of_line
标记为1,__out of line 形式实现了 用哈希表数据结构__保存 对象(*referent
)的所有弱引用的地址。referrers
指向weak_referrer_t
的数组(对象的所有弱引用的地址 的数组),该数组用于保存哈希表的所有元素,num_refs
是哈希表中保存的元素个数,占用63位,mask
记录哈希表的长度、max_hash_displacement
记录哈希表的最大冲突个数,具体使用后文会介绍。
- inline 形式:
具体分析下weak_entry_t
的内存结构。
referent
是1个指针,占8字节;union
联合体为 inline 形式时保存inline_referrers
中的4个指针,占32字节;根据结构体内存对齐规则,out of line 形式保存referrers
、mask
、max_hash_displacement
占24字节,num_refs
、out_of_line
共同占用8字节,由于联合体的成员间共享内存空间,因此union
联合体固定占用32字节。
inline 和 out_of_line 形式下weak_referrer_t
的内存结构 分别如下图所示。
如图红色标识的指针weak_entry_t* entry
指向地址0x100BB13400
的内存块,内存块保存weak_entry_t
结构体。当weak_entry_t
使用 inline 形式保存时,图中绿色标记的0x100BB134010内存块保存的是 对象的弱引用的内存地址;当weak_entry_t
使用 inline 形式保存时,图中相同位置的内存块,最低位保存 out_of_line 标记,其余63位保存num_refs。
为什么这块内存的最低位可以标记是否 inline 呢?因为64位机CPU每次访问内存的必定是 以64的倍数为起始地址 读出64位(8字节)数据,为保证CPU读取内存的效率,一个指针的起始地址必然为8的倍数(防止读取一个指针的内容访问两次内存),即64位机指针的地址后3位必定为0。再加之inline_referrers
的单元格内容为空时保存0x0
,最低位也是0。因此,当weak_entry_t
每个单元格中的内容的最后一位均为0时,则必定是采用了 inline 形式保存 对象的弱引用的地址。
具体内存地址对齐的规则可以看这篇文章:内存地址对齐。
2.1.3 weak_table_t结构体
SideTable
中包含weak_table_t
类型的weak_table
成员。weak_table_t
结构体是用于保存weak_entry_t
哈希表,源代码如下:
struct weak_table_t {
weak_entry_t *weak_entries;
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
该结构体中:
weak_entries
指向weak_entry_t
结构体的数组,该数组保存哈希表的所有元素,一个元素对应 一个对象weak_entry_t
地址;num_entries
是哈希表中保存的weak_entry_t
元素的个数;mask
记录哈希表的长度、max_hash_displacement
记录哈希表的最大冲突个数,具体使用后文会介绍;
2.1.4 总结:
-
每个被弱引用对象,必然有一个
weak_entry_t
与之对应,weak_entry_t
保存了该对象的地址 以及所有 弱引用了该对象 的引用的地址; -
SideTable
中weak_table_t
类型的weak_table
成员,保存了SideTable
管理的对象中 所有被弱引用的对象 的weak_entry_t
。
2.2 storeWeak 源码解析
实现弱引用的关键代码在template <bool HaveOld, bool HaveNew, bool CrashIfDeallocating> static id storeWeak(id *location, objc_object *newObj)
函数中。删除断言、加解锁、等待类初始化等代码,得到简化源码如下。参数含义:
HaveOld
:是否有旧值;HaveNew
:是否指定新值;CrashIfDeallocating
:若为true
,传入的newObj
不支持weak
类型或野指针时程序将中断运行;若为false
,传入的newObj
不支持weak
类型或野指针时置新值为nil
;location
:weak指针旧值地址(类型objc_object **
);newObj
:weak指针新值(类型objc_object *
);
template <bool HaveOld, bool HaveNew, bool CrashIfDeallocating>
static id
storeWeak(id *location, objc_object *newObj)
{
id oldObj;
SideTable *oldTable;
SideTable *newTable;
retry:
if (HaveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (HaveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
if (HaveOld && *location != oldObj) {
goto retry;
}
if (HaveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
if (HaveNew) {
newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table,
(id)newObj, location,
CrashIfDeallocating);
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}
*location = (id)newObj;
}
return (id)newObj;
}
2.2.1 storeWeak 弱引用指向的新对象的处理
首先抽出HaveNew
为true
的代码逻辑:
- 若weak指针指向的新对象
newObj
,则获取newObj
的SideTable
赋值给newTable
; - 调用
weak_register_no_lock(...)
函数 注册新对象; - 若新对象
newObj
非空,调用setWeaklyReferenced_nolock(...)
函数 设置弱引用新对象newObj
;
注意:之所以先分析新对象处理,是因为这一部分是从无到有的过程,必然包含了很多理解旧对象处理的关键信息。后文阅读代码的顺序都会用到该原则:优先关注从无到有的逻辑。
2.2.1.1 weak_register_no_lock 注册新对象
删除不关心代码,weak_register_no_lock(...)
函数关键代码如下。其功能是将弱引用对象的地址referent_id
以及对象的弱引用的地址referrer_id
添加到weak_table
中。
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, bool crashIfDeallocating)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
if (!referent || referent->isTaggedPointer()) return referent_id;
weak_entry_t *entry;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer);
}
else {
weak_entry_t new_entry;
new_entry.referent = referent;
new_entry.out_of_line = 0;
new_entry.inline_referrers[0] = referrer;
for (size_t i = 1; i < WEAK_INLINE_COUNT; i++) {
new_entry.inline_referrers[i] = nil;
}
weak_grow_maybe(weak_table);
weak_entry_insert(weak_table, &new_entry);
}
return referent_id;
}
关注到判断逻辑if ((entry = weak_entry_for_referent(weak_table, referent)))
判断逻辑,其含义是从weak_table
中查询 新添加对象引用referent
的weak_entry
。先不看weak_entry_for_referent(...)
的实现逻辑,初始状态下该判断条件一定为false
,所以先看else
中的代码。
else
块中的处理过程如下:
- 构建
weak_entry_t
类型的结构体new_entry
; new_entry
保存 新对象地址;new_entry
指定初始使用 inline 形式保存 新对象的弱引用的地址;new_entry
的inline_referrers[0]
保存 新对象的弱引用的地址;new_entry
的inline_referrers
剩下的三个空间nil
(for
循环);- 调用__
weak_grow_maybe(weak_table)
函数__,在有需要时,扩展weak_table
存储空间; weak_entry_insert(weak_table, &new_entry)
将new_entry
添加到weak_table
。
2.2.1.1.1 weak_grow_maybe 扩展weak_table长度
接下来看看weak_grow_maybe(...)
的内部逻辑,代码如下:
#define TABLE_SIZE(entry) (entry->mask ? entry->mask + 1 : 0)
static void weak_grow_maybe(weak_table_t *weak_table)
{
size_t old_size = TABLE_SIZE(weak_table);
if (weak_table->num_entries >= old_size * 3 / 4) {
weak_resize(weak_table, old_size ? old_size*2 : 64);
}
}
其含义是,若weak_table
的num_entries
数组的占用率超过3/4,则调用__weak_resize(weak_table_t *weak_table, size_t new_size)
函数__ 扩展weak_table_t
的weak_entries
数组的长度。
至此,可以知道weak_table_t
中的mask
的用处。mask
是一个全1的二进制数,mask + 1
是weak_table_t
的weak_entries
数组的长度。每次扩展weak_table_t
增加1倍的长度,也就是mask = 2 * mask + 1
,至少分配64个指针的长度(64*8字节)。也就是说weak_table_t
的weak_entries
数组长度若不为0,则其长度至少为64。
2.2.1.1.2 weak_resize 重新哈希 weak_table
继续看weak_resize(weak_table_t *weak_table, size_t new_size)
的代码:
static void weak_resize(weak_table_t *weak_table, size_t new_size)
{
size_t old_size = TABLE_SIZE(weak_table);
weak_entry_t *old_entries = weak_table->weak_entries;
weak_entry_t *new_entries = (weak_entry_t *)
calloc(new_size, sizeof(weak_entry_t));
weak_table->mask = new_size - 1;
weak_table->weak_entries = new_entries;
weak_table->max_hash_displacement = 0;
weak_table->num_entries = 0; // restored by weak_entry_insert below
if (old_entries) {
weak_entry_t *entry;
weak_entry_t *end = old_entries + old_size;
for (entry = old_entries; entry < end; entry++) {
if (entry->referent) {
weak_entry_insert(weak_table, entry);
}
}
free(old_entries);
}
}
其处理过程如下:
- 记录
weak_table
的旧weak_entries
为old_entries
; - 分配
new_size
长度的内存空间new_entries
用于保存新的weak_entries
; weak_table->weak_entries
赋值为new_entries
;weak_table
的max_hash_displacement
、num_entries
置0,两者会在接下来的调用weak_entry_insert(...)
时更新;- 若
old_entries
有值,则需要调用weak_entry_insert(...)
函数 将其中的所有weak_entry_t
重新哈希保存到new_entries
中(for
循环),完成重新哈希释放old_entries
占用的内存(free(old_entries)
);
2.2.1.1.3 weak_entry_insert 元素插入 weak_table
继续看weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
实现weak_table
的单个weak_entry_t
重新哈希的代码:
static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
{
weak_entry_t *weak_entries = weak_table->weak_entries;
assert(weak_entries != nil);
size_t index = hash_pointer(new_entry->referent) & (weak_table->mask);
size_t hash_displacement = 0;
while (weak_entries[index].referent != nil) {
index = (index+1) & weak_table->mask;
hash_displacement++;
}
weak_entries[index] = *new_entry;
weak_table->num_entries++;
if (hash_displacement > weak_table->max_hash_displacement) {
weak_table->max_hash_displacement = hash_displacement;
}
}
理解上述代码的关键在于理解这两行代码:
-
size_t index = hash_pointer(new_entry->referent) & (weak_table->mask)
:其中,hash_pointer(...)
用于计算指针的哈希值(代码在下方,不过实现细节可以不纠结),之所以哈希的是new_entry->referent
(对象的地址)是因为 区分weak_table->weak_entries
中的weak_entry_t
的标准是weak_entry_t
所保存的对象。 -
while (weak_entries[index].referent != nil)
:以线性探测法解决哈希冲突,若weak_entries
内 哈希计算的索引 已被占用,则递增索引同时递增hash_displacement
计数进入下一次循环迭代。这里之所以用index = (index+1) & weak_table->mask
是为了能在探测索引到达weak_table
的最大索引时归0; -
weak_table->max_hash_displacement = hash_displacement
:max_hash_displacement
是用于记录weak_table
的最大冲突数量。在使用对象地址 在weak_table->weak_entries
中哈希搜索weak_entry_t
时,迭代次数超过max_hash_displacement
可直接返回nil
。其作用是尽量压低哈希搜索的迭代次数;
对哈希函数有兴趣的话其代码如下:
static inline uintptr_t hash_pointer(objc_object *key) {
return ptr_hash((uintptr_t)key);
}
#if __LP64__
static inline uint32_t ptr_hash(uint64_t key)
{
key ^= key >> 4;
key *= 0x8a970be7488fda55;
key ^= __builtin_bswap64(key);
return (uint32_t)key;
}
#else
static inline uint32_t ptr_hash(uint32_t key)
{
key ^= key >> 4;
key *= 0x5052acdb;
key ^= __builtin_bswap32(key);
return key;
}
#endif
2.2.1.1.4 weak_entry_for_referent 获取对象的 weak_entry
分析完weak_register_no_lock(...)
中if ((entry = weak_entry_for_referent(weak_table, referent)))
判断逻辑的else
分支,再回头看weak_entry_for_referent(..)
以及if
分支中的append_referrer(weak_entry_t *entry, objc_object **new_referrer)
,虽然代码稍长但也会稍微不那么吃力。
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
函数代码如下。功能是使用referent
(对象的地址)计算哈希索引,使用线性探测法迭代搜索weak_table->weak_entries
,迭代次数不超过weak_table->max_hash_displacement
。
static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
assert(referent);
weak_entry_t *weak_entries = weak_table->weak_entries;
if (!weak_entries) return nil;
size_t index = hash_pointer(referent) & weak_table->mask;
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;
}
}
return &weak_table->weak_entries[index];
}
2.2.1.1.5 append_referrer 添加对象的弱引用地址
当在weak_table
中哈希搜索到对象referent
对应的weak_entry_t
类型的entry
时,调用append_referrer(entry, referrer)
函数将 对象的弱引用的地址referrer
添加到entry
中。代码如下,这里将处理过程通过注释加到代码中:
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
// 1、若entry使用inline形式保存对象的弱引用的地址new_referrer
if (! entry->out_of_line) {
// 2、在inline_referrers中搜索空单元,若存在则填入new_referrer并返回
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == nil) {
entry->inline_referrers[i] = new_referrer;
return;
}
}
// 3、若inline_referrers没有空单元,则分配4个单元(32字节)长度的new_entry
// 用于保存inline_referrers中的弱引用地址数组,并赋值给entry->referrers,将
// entry的保存形式指定为out of line,mask的指定参照weak_table的mask,并初始化
// max_hash_displacement为0
//
// 注意:这里没有对new_referrers哈希处理,是因为entry的占用度为100%必然会触发
// 后面的重新哈希。
weak_referrer_t *new_referrers = (weak_referrer_t *)
calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
new_referrers[i] = entry->inline_referrers[I];
}
entry->referrers = new_referrers;
entry->num_refs = WEAK_INLINE_COUNT;
entry->out_of_line = 1;
entry->mask = WEAK_INLINE_COUNT-1;
entry->max_hash_displacement = 0;
}
assert(entry->out_of_line);
// 4、若entry的referrer的占用率超过3/4则触发重新哈希,重新哈希过程参考weak_table
// grow_refs_and_insert函数跟前面weak_grow_maybe差不多,后面会贴出代码不做赘述。
if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
return grow_refs_and_insert(entry, new_referrer);
}
// 5、使用new_referrer计算哈希值,使用线性探测法将new_referrer添加到entry->referrers中,
// 处理跟前面weak_entry_insert差不多,不做赘述。w_hash_pointer等价于前面的hash_pointer
size_t index = w_hash_pointer(new_referrer) & (entry->mask);
size_t hash_displacement = 0;
while (entry->referrers[index] != NULL) {
index = (index+1) & entry->mask;
hash_displacement++;
}
if (hash_displacement > entry->max_hash_displacement) {
entry->max_hash_displacement = hash_displacement;
}
weak_referrer_t &ref = entry->referrers[index];
ref = new_referrer;
entry->num_refs++;
}
__attribute__((noinline, used))
static void grow_refs_and_insert(weak_entry_t *entry,
objc_object **new_referrer)
{
assert(entry->out_of_line);
size_t old_size = TABLE_SIZE(entry);
size_t new_size = old_size ? old_size * 2 : 8;
size_t num_refs = entry->num_refs;
weak_referrer_t *old_refs = entry->referrers;
entry->mask = new_size - 1;
entry->referrers = (weak_referrer_t *)
calloc(TABLE_SIZE(entry), sizeof(weak_referrer_t));
entry->num_refs = 0;
entry->max_hash_displacement = 0;
for (size_t i = 0; i < old_size && num_refs > 0; i++) {
if (old_refs[i] != nil) {
append_referrer(entry, old_refs[I]);
num_refs--;
}
}
// Insert
append_referrer(entry, new_referrer);
if (old_refs) free(old_refs);
}
2.2.1.2 setWeaklyReferenced_nolock 设置弱引用新对象
完成弱引用注册后,新对象newObj
需要调用setWeaklyReferenced_nolock(...)
方法标记对象被弱引用。因为对象在析构时,若对象被弱引用,则需要将这些弱引用全部置nil
。代码如下:
inline void
objc_object::setWeaklyReferenced_nolock()
{
retry:
isa_t oldisa = LoadExclusive(&isa.bits);
isa_t newisa = oldisa;
if (!newisa.indexed) return sidetable_setWeaklyReferenced_nolock();
if (newisa.weakly_referenced) return;
newisa.weakly_referenced = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
}
这里,在从Runtime源代码解读内存管理机制——Retain/Release中介绍的isa.bits
再次粉墨登场。处理逻辑如下:
- 若
isa.bits
的indexed
位为1(只使用isa
或使用isa
联合SideTable
保存对象引用计数),则设置isa.bits
的弱引用标记位为1; - 若
isa.bits
的indexed
位为0(只使用SideTable
保存对象引用计数),则调用sidetable_setWeaklyReferenced_nolock()
方法,其源代码如下,只是简单设置了SideTable
的refcnts
中该对象的引用计数信息的弱引用标记位为1,代码如下:
void
objc_object::sidetable_setWeaklyReferenced_nolock()
{
SideTable& table = SideTables()[this];
table.refcnts[this] |= SIDE_TABLE_WEAKLY_REFERENCED;
}
2.2.1.3 总结storeWeak对新对象的处理
storeWeak(...)
函数对新对象的核心处理流程如下:
- 根据新对象
newObj
定位到目标SideTable
,从中取出weak_table
; - 判断
weak_table
的使用率是否超过3/4,超过则对其扩容并重新哈希; - 在
weak_table
的weak_entries
中,通过newObj
哈希,使用线性探测法迭代搜索newObj
对应的weak_entry
; - 若搜索不到,则在
weak_table
的weak_entries
中,通过newObj
哈希,使用线性探测法添加newObj
的weak_entry
并指定使用inline形式保存referrers
; - 至此,得到新对象的
weak_entry
; - 若
weak_entry
的referrers
使用inline形式保存,则在inline_referrer
中的空位填入newObj
的弱引用的地址newReferrer
,跳过下一步;若无空位,则将referrers
保存形式转换为out of line,并将inline_referrer
中的数据转移到referrers
; - 若
weak_entry
的referrers
使用out of line形式保存,则判断referrers
的使用率是否超过3/4,超过则对其扩容并重新哈希。在referrers
中,通过newReferrer
哈希,使用线性探测法添加newReferrer
; - 至此,新对象的
weak_entry
添加到weak_table
中; - 标记新对象被弱引用。若
isa.indexed
为0,则找到对象在SideTable中记录的引用计数信息,将弱引用标记置为1;若isa.indexed
为1,则将新对象的isa.bits
的弱引用标记置为1。
重点一:
weak_entry_t
实现了保存 一个对象的所有weak_referrer_t
(对象的所有弱引用的地址)的哈希表,使用weak_referrer_t
计算哈希索引,哈希表长度可动态扩展使用线性探测法解决冲突。
重点二:
weak_table_t
实现了保存一组对象的weak_entry_t
的哈希表,使用对象地址referrer
计算哈希索引,哈希表长度可动态扩展使用线性探测法解决冲突。
2.2.2 storeWeak 弱引用指向的旧对象的处理
上一节介绍了storeWeak(...)
处理新对象的过程,本节接着介绍storeWeak(...)
处理旧对象的过程。先从中抽出HaveOld
为true
的代码逻辑:
- 若弱引用指向的旧对象
oldObj
非空,则获取oldObj
的SideTable
赋值给oldTable
; - 调用
weak_unregister_no_lock(...)
函数;
weak_unregister_no_lock(...)
方法的代码如下:
void weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
weak_entry_t *entry;
if (!referent) return;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
remove_referrer(entry, referrer);
bool empty = true;
if (entry->out_of_line && entry->num_refs != 0) {
empty = false;
}
else {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i]) {
empty = false;
break;
}
}
}
if (empty) {
weak_entry_remove(weak_table, entry);
}
}
}
有前面打下的基础,上面的代码逻辑已经比较明了:
- 若旧对象
referent
为空则直接返回; - 若
weak_table
中存在旧对象referent
对应的entry
:则调用remove_referer(entry, referrer)
从entry
中删除该弱引用的地址referrer
;再判断entry
是否为空,若使用out of line形式保存referrers
则只要查询num_refs
是否为0,若使用inline则需要遍历inline_referrers
。entry
为空时调用weak_entry_remove(weak_table, entry)
将entry
从weak_table
中删除;
继续看remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
的代码,处理过程写在代码注释中:
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
if (! entry->out_of_line) {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == old_referrer) {
entry->inline_referrers[i] = nil;
return;
}
}
_objc_inform("Attempted to unregister unknown __weak variable "
"at %p. This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
old_referrer);
objc_weak_error();
return;
}
size_t index = w_hash_pointer(old_referrer) & (entry->mask);
size_t hash_displacement = 0;
while (entry->referrers[index] != old_referrer) {
index = (index+1) & entry->mask;
hash_displacement++;
if (hash_displacement > entry->max_hash_displacement) {
_objc_inform("Attempted to unregister unknown __weak variable "
"at %p. This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
old_referrer);
objc_weak_error();
return;
}
}
entry->referrers[index] = nil;
entry->num_refs--;
}
最后看weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
的代码,处理过程写在代码注释中:
static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
{
if (entry->out_of_line) free(entry->referrers);
bzero(entry, sizeof(*entry));
weak_table->num_entries--;
weak_compact_maybe(weak_table);
}
static void weak_compact_maybe(weak_table_t *weak_table)
{
size_t old_size = TABLE_SIZE(weak_table);
// Shrink if larger than 1024 buckets and at most 1/16 full.
if (old_size >= 1024 && old_size / 16 >= weak_table->num_entries) {
weak_resize(weak_table, old_size / 8);
// leaves new table no more than 1/2 full
}
}
至此,总结一下storeWeak对旧对象的核心处理流程:
- 根据旧对象
oldObj
定位到目标SideTable
,从中取出weak_table
; - 若根据旧对象找到
weak_table
中oldObj
的weak_entry
, - 若
weak_entry
保存referrers
的形式为inline,则遍历inline_referrers
删除当前弱引用的地址;若weak_entry
保存referrers
的形式为out of line,则使用当前弱引用的地址计算哈希值,用线性探测法搜索referrers
,从中删除当前弱引用的地址; - 至此已从
weak_entry
的referrers
删除当前弱引用的地址; - 若移除当前指针后,
referrers
中的referrer
数量为0,则将旧对象对应entry
从weak_table
的weak_entries
中移除,移除同样通过oldObj
的引用计算哈希值,使用线性探测法搜索; weak_table
移除weak_entries
后,若weak_entries
的长度超过1024且weak_entries
的占用率不超过1/16,则将weak_entries
的长度缩短为原1/8并重新哈希weak_entries
;
三、总结
最后用下图表示SideTable
、weak_table_t
、weak_entry_t
、weak_referent_t
、weak_referrer_t
之间的关系。图比较大,看不清可以下载原图查看。注意:
- 锐利箭头表示指向关系(左侧表示保存内存地址,右侧表示地址中所存储的内容);
- 初始状态下内存中存在两个对象 oldObj 和 newObj,假设两个对象的引用计数均保存在同一张
SideTable
中,用 inline 形式保存 oldObj 的所有弱引用的地址,用 out of line 形式保存 newObj 的所有弱引用的地址; - 弱引用 weakRef 指向 oldObj;
- 图中左侧的 oldObj 和右侧的 oldObj 是同一个对象,左侧的 weakRef 和 右侧的 weakRef 是同一个对象引用,均指向 oldObj,左侧的weakReferrer 和右侧的 weakReferrer 均是 weakRef 的地址;
weak_entries
哈希表动态调整容量、refferrers
哈希表动态调整容量、记录对象是否被弱引用的过程不作图示。
接下来调用 storeWeak(weakReferrer, newObj)
让 weakRef 指向 newObj,首先 storeWeak
处理旧对象的示意图。黄色平面箭头表示操作流程,黄色标识块表示流程涉及的数据。处理完成后结果如左上角所示,weakRef 指向nil
。
最后是storeWeak
处理新对象的示意图。红色平面箭头表示操作流程,红色标识块表示流程涉及的数据。处理完成后结果如左上角所示,weakRef 指向newObj。