weak 分析思路:汇编+源码
- 打开汇编
- 定位到
objc_initWeak
- 全局搜索
objc_initWeak
,找到实现的地方
- 定位到重点函数
storeWeak
- 分析到函数重点
storeWeak
的核心实现
- 定位到重点函数
weak_register_no_lock
weak 修饰原理:流程
变量说明
在流程中会一直遇到以下几个变量
weak_table
:weak_table_t
类型,全局弱引用表
entry
:weak_entry_t
类型,weak 变量的存储实体
referent
:objc_object *
类型,对象的引用,这里表示被弱引用的对象指针
referrer
:objc_object **
类型,引用的地址,这里表示弱引用的对象指针的指针
入口函数
weak源码流程图objc_initWeak
storeWeak
1. class_initialize
条件:如果这个类正在初始化,则应该先初始化class_initialize
2. weak_unregister_no_lock
条件:如果有旧的值,则先清理 weak_unregister_no_lock
-
非空判断,
if (!referent) return;
-
从
weak_table
中取weak_entry_t
——弱引用的集合entry
-
如果
entry
存在移除旧的弱引用:remove_referrer(entry, referrer);
-
如果
entry
中的referrer
为空,则从weak_table
中移除这个entry
if (empty) weak_entry_remove(weak_table, entry);
3. weak_register_no_lock
条件:如果有新的值,则存储 weak_register_no_lock
-
如果在
weak_table
中找到了弱引用对象referent
的弱引用的集合enter
if ((entry = weak_entry_for_referent(weak_table, referent)))
-
append_referrer
- 如果下面条件成功,先扩容再插入
if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) grow_refs_and_insert(entry, new_referrer);
- 保存新的弱引用指针的地址
// 保存新的弱引用指针的地址 weak_referrer_t &ref = entry->referrers[index]; ref = new_referrer; // 集合数量 +1 entry->num_refs++;
-
如果不存在这个
弱引用的集合enter
-
创建一个新的
弱引用的集合enter
:weak_entry_t new_entry(referent, referrer);
作用1:创建一个新的弱引用的集合enter
作用2:保存这个弱引用指针的地址 -
如果 weak_table 需要扩容,则去扩容:
weak_grow_maybe(weak_table);
-
在 weak_table 中插入这个新的
弱引用的集合enter
:
weak_entry_insert(weak_table, &new_entry);
-
weak 释放原理:流程
在ARC机制下,当一个对象的引用计数为0的时候,会向他发送一个 release 消息,而在 release 的实现中,会执行 dealloc 方法,在 dealloc 方法中会执行 weak_clear_no_lock(&table.weak_table, (id)this);
方法来清空weak引用
if (*referrer == referent) {
*referrer = nil;
}
通过 引用的地址 将引用(weak变量)置空
1. dealloc 流程
_objc_rootDealloc(self);
--> obj->rootDealloc();
----> object_dispose((id)this);
------> objc_destructInstance(obj);
--------> obj->clearDeallocating();
----------> clearDeallocating_slow();
------------> if (isa.weakly_referenced) {
------------> weak_clear_no_lock(&table.weak_table, (id)this);
------------> }
2. weak_clear_no_lock 流程
参数
weak_table
:全局弱引用表
referent
:将要被释放的弱引用对象
- 获取
弱引用的集合entry
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
- 遍历 集合enter 中的弱引用指针,并置空
*referrer = nil;
- 移除
entry集合
weak_entry_remove(weak_table, entry);
3. weak_entry_remove 流程
- 释放
entry集合
的引用
free(entry->referrers);
- weak_table计数减一
weak_table->num_entries;
- 重新设置
weak_table
的大小
weak_compact_maybe(weak_table);
Weak 修饰原理:内存表
上面介绍了 weak修饰
和 weak释放
的流程,但他们在内存中到底是怎么存在的,并没有分析到,下面将会进行详细说明
1. 什么是 weak变量?
先来看一段代码
LGTeacher* teacher = [LGTeacher alloc];
__weak id wobjc1 = teacher;
__weak id wobjc2 = teacher;
__strong id sobjc1 = wobjc1;
__strong id sobjc2 = wobjc2;
NSLog(@"--%@, --%@, --%@, --%@,", wobjc1, wobjc2, sobjc1, sobjc2);
输出结果
--<LGTeacher: 0x1012525f0>, --<LGTeacher: 0x1012525f0>, --<LGTeacher: 0x1012525f0>, --<LGTeacher: 0x1012525f0>,
用 weak 修饰的变量是 weak变量,代码中的 wobjc1
和 wobjc2
是用 weak修饰
的变量,sobjc1
和 sobjc2
使用 strong修饰
的变量,但是我们从输出结果中可以看到,他们的所有值都是一样的,那 weak
和 strong
还有啥区别呢?
weak和strong变量的存储方式不一样,存储方式后面再做介绍。
这四个变量的值都是 teacher,teacher 表示对象的首地址
,也就是输出结果中的 0x1012525f0 这个地址。如果不看 __weak
和 __strong
,上面的四个表达式就是一个单纯的赋值运算符,将右边的值赋给左边。因为有了这两个修饰符,他们有了不一样的含义,在内存中的存储方式发生了改变。
引用、对象、类型
还是上面那段代码。
引用对象类型- 对象:alloc分配的一块内存
- 引用:teacher-强引用,
wobjc1
和wobjc2
-弱引用,sobjc1
和sobjc2
-强引用 - 类型:id、LGTeacher,都是类型。在对类的结构的探索中,我们可以直接拿到对象的某个属性的地址,但在开发中,我们并不能直接通过这个地址去修改属性的值,只能通过变量根据类型的setter方法去设置属性的值,这是对内存的一种保护机制。
下面开始来介绍表的原理
3. weak 修饰原理:内存表
例如:程序在执行下面这一行代码的时候,会访问哪些内存相关的操作,都在下面的流程图中。
__weak id weakRef = objc
referent
:weakRef,表示这个弱引用变量
referrer
:&weakRef,表示这个弱引用变量的地址
变量
weak_table
:weak_table_t
类型,全局弱引用表
entry
:weak_entry_t
类型,weak 变量的存储实体
referent
:objc_object *
类型,对象的引用,这里表示被弱引用的对象指针
referrer
:objc_object **
类型,引用的地址,这里表示弱引用的对象指针的指针
大致流程
- 先从 SideTable 中找到 weakTable;
- 然后从 weakTable 中找到 entry,如果没有就新建一个;
- 将 weak 引用存入到 entry中;
lldb探索
- 第一次进来:weak_table 中是空的
- 执行 else 流程之后,weak_table 中出现了一个 entry
- 第二次进来:entry 中有值存在
第一次存成功了,既然是第二次进来,那不把第一次存的东西找到,肯定是浑身难受的
-
输出 entry 信息:
entryp *entry
-
输出 referent,weak_entry_t 的key值,对象的首地址:
referentp undisguise(18446744069395690144)
-
输出 referrers 中的 weak_referrer_t:
weak_referrer_tp $16.referrers
和p $16.inline_referrers[0]
。注释中解释了这个值进行了一些操作,比如指针对齐,所以无法找到二级地址以及一级地址,这个问题后面再探索。
-
insert 之后,entry 中有两个值。
image
通过上面这一波操作,weak变量已经从隐身状态显型了,呈现到了我们的眼前,再也不是仅仅存在脑海中的幻象了。
为什么 entry 中存的是 referrer(引用的地址)?
大家应该都听过一句话——weak变量会自动置为nil
,原理就在这个 referrer
中。
通过 引用的地址 将引用(weak变量)置空
4. 弱引用表:weak_table_t
1. weak_table_t 结构
/**
* The global weak references table. Stores object ids as keys,
* and weak_entry_t structs as their values.
*/
struct weak_table_t {
weak_entry_t *weak_entries;
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
注释中说的很明白了
- 这是一个全局的弱引用表
- key:对象id 作为 key
- value:weak_entry_t 结构作为 value
2. weak_entry_t 的查找流程
static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
size_t begin = hash_pointer(referent) & weak_table->mask;
size_t index = begin;
size_t hash_displacement = 0;
while (weak_table->weak_entries[index].referent != referent) {
index = (index+1) & weak_table->mask;
if (index == begin) bad_weak_table(weak_table->weak_entries);
hash_displacement++;
if (hash_displacement > weak_table->max_hash_displacement) {
return nil;
}
}
return &weak_table->weak_entries[index];
}
对于 hash 表来说,重点就是下标的计算,计算 weak_table 中下标的代码:
size_t begin = hash_pointer(referent) & weak_table->mask;
size_t index = begin;
size_t hash_displacement = 0;
while (weak_table->weak_entries[index].referent != referent) {
index = (index+1) & weak_table->mask;
if (index == begin) bad_weak_table(weak_table->weak_entries);
hash_displacement++;
if (hash_displacement > weak_table->max_hash_displacement) {
return nil;
}
}
计算完下标之后,返回 weak_entry_t 的指针
,以便外部可以修改,进行插入或删除的操作
return &weak_table->weak_entries[index];
5. weak实体表:weak_entry_t
1. weak_entry_t 源码
struct weak_entry_t {
DisguisedPtr<objc_object> referent;
union {
struct {
weak_referrer_t *referrers;
uintptr_t out_of_line_ness : 2;
uintptr_t num_refs : PTR_MINUS_2;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
// out_of_line_ness field is low bits of inline_referrers[1]
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
bool out_of_line() {
return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
}
weak_entry_t& operator=(const weak_entry_t& other) {
memcpy(this, &other, sizeof(other));
return *this;
}
weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
: referent(newReferent)
{
inline_referrers[0] = newReferrer;
for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
inline_referrers[i] = nil;
}
}
};
可以看出来,这里并没有做多少运算的操作,主要是两个属性,
2. 属性 referent
这是保存对象地址的一个属性,作为 hash 表的 key 值。
DisguisedPtr<objc_object> referent;
3. 属性 referrers
这是一个联合体类型,存的就是引用的地址(二级指针)。
union {
struct {
weak_referrer_t *referrers;
uintptr_t out_of_line_ness : 2;
uintptr_t num_refs : PTR_MINUS_2;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
// out_of_line_ness field is low bits of inline_referrers[1]
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
4. weak_referrer_t:DisguisedPtr 类
-
weak_referrer_t 类型
typedef DisguisedPtr<objc_object *> weak_referrer_t;
-
DisguisedPtr 类:提供两个静态方法,分别是:
- 将地址取反转成整型存储,这里可以理解为 encode
- 对存储的整型取反转成指针返回,这里可以理解为 decode
template <typename T> // 泛型 class DisguisedPtr { uintptr_t value; // encode static uintptr_t disguise(T* ptr) { return -(uintptr_t)ptr; } // decode static T* undisguise(uintptr_t val) { return (T*)-val; } …… }
其实在上面输出
referentreferent
属性的时候,就用到了这个 decode 操作,苹果这么做的目的是,为了不让 leak 跟踪这些 weak 地址。暂时还没有找到比较有说服力的理由,后面遇到了再补充。
5. append_referrer 方法
size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
size_t index = begin;
size_t hash_displacement = 0;
while (entry->referrers[index] != nil) {
hash_displacement++;
index = (index+1) & entry->mask;
if (index == begin) bad_weak_table(entry);
}
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++;
这个方法跟上面的 weakTable 找 entry 的方法流程是一致的:
- 计算下标
index
; - 往
index
处插入weak_referrer_t
;
weak 原理总结
- 搞清楚引用、对象、类型这些名词的含义;
- 搞清楚 weak 存储的流程;
- 搞清楚 weak 释放的流程;
- 搞清楚 weak 是怎么存储到内存中的,是存在内存的什么地方;
- weak实体存的是 weak变量的地址,为了在释放的时候能够将weak变量置空;
- weak实体在存 weak变量的地址的时候,进行了一步取反操作,读的时候再取反读;
网友评论