weak 的实现

weak 表示的是一个弱引用,不会增加对象的引用计数,并且在所指向的对象被释放之后,weak 指针会被置为nil。weak 引用通常是用于处理循环引用的问题。

实现原理:

  • 初始化:

runtime 调用 objc_initWeak 函数,初始化一个新的 weak 指针指向对象的地址。

  • 添加引用:

objc_initWeak 调用 objc_storeWeak 来更新指针指向,创建对应的弱引用表。

  • 释放:

调用 clearDeallocating 函数,先根据对象地址获取所有 weak 指针地址的数组;再遍历这个数组把其中的数据置为 nil;然后把这个 entry 从 weak 表中删除;最后清理对象的记录。

1.初始化

#示例:

1
2
NSObject *newObj = [[NSObject alloc] init];
id __weak weakObj = newObj;

初始化 weak 变量时,Runtime 会调用 NSObject.mm 中的 objc_initWeak 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/** 
* Initialize a fresh weak pointer to some object location.
* It would be used for code like:
*
* (The nil case)
* __weak id weakPtr;
* (The non-nil case)
* NSObject *o = ...;
* __weak id weakPtr = o;
*
* @param location Address of __weak ptr.
* @param newObj Object ptr.
*/

id objc_initWeak(id *location, id newObj) {
// 查看对象实例是否有效
// 无效对象直接导致指针释放
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak(location, (objc_object*)newObj);
}

对应的编译器模拟代码:

1
2
id weakObj;
objc_initWeak(&weakObj, newObj);

objc_initWeakweakObj对象的指针及newObj对象作为参数,最终调用storeWeak方法。

2.添加引用

objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表,详细实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
id objc_storeWeak(id *location, id newObj)
{
id oldObj;
SideTable *oldTable;
SideTable *newTable;
......
// Acquire locks for old and new values.
// Order by lock address to prevent lock ordering problems.
// Retry if the old value changes underneath us.
retry:
oldObj = *location;
oldTable = SideTable::tableForPointer(oldObj);
newTable = SideTable::tableForPointer(newObj);
......
if (*location != oldObj) {
OSSpinLockUnlock(lock1);
#if SIDE_TABLE_STRIPE > 1
if (lock1 != lock2) OSSpinLockUnlock(lock2);
#endif
goto retry;
}
if (oldObj) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
if (newObj) {
newObj = weak_register_no_lock(&newTable->weak_table, newObj,location);
// weak_register_no_lock returns NULL if weak store should be rejected
}
// Do not set *location anywhere else. That would introduce a race.
*location = newObj;
......
return newObj;
}

先是根据 weak 指针找到其指向的老对象:

1
oldObj = *location;

然后获取到与新旧对象相关的 SideTable 对象:

1
2
oldTable = SideTable::tableForPointer(oldObj);
newTable = SideTable::tableForPointer(newObj);

然后在老对象的 弱引用表 中移除指向信息,而在新对象的 弱引用表 中建立关联信息:

1
2
3
4
5
6
7
if (oldObj) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
if (newObj) {
newObj = weak_register_no_lock(&newTable->weak_table, newObj,location);
// weak_register_no_lock returns NULL if weak store should be rejected
}

接下来将弱引用指针指向新的对象:

1
*location = newObj;

最后返回这个新对象:

1
return newObj;

小结:objc_storeWeak最终将weakObj的指针指向newObj,同时将二者保存到SideTable中。


2.1.SideTable

为了管理所有对象的引用计数和 weak 指针,系统创建了一个全局的 SideTables。它是一个全局的 hash 表,里面保存的是 SideTable 结构体。SideTables 使用对象的内存地址作为 key,通过 SideTables[key] 来得到 SideTable。这个 SideTable 的数据结构定义如下:

1
2
3
4
5
6
7
8
struct SideTable {
// 保证原子操作的自旋锁
spinlock_t slock;
// 引用计数的 hash 表
RefcountMap refcnts;
// 全局的弱引用 hash 表
weak_table_t weak_table;
}

2.2.弱引用表

runtime 维护了一个弱引用表,即上面 SideTable 结构体中最后一个参数 weak_table_t。它存储了某个对象的所有弱引用信息,其数据结构如下:

1
2
3
4
5
6
7
8
9
10
struct weak_table_t {
// 保存了所有指向指定对象的 weak 指针
weak_entry_t *weak_entries;
// 存储空间
size_t num_entries;
// 参与判断引用计数辅助量
uintptr_t mask;
// hash key 最大偏移值
uintptr_t max_hash_displacement;
};

其中 weak_entry_t 是存储在弱引用表中的一个内部结构体,它负责维护和存储指向一个对象的所有弱引用 hash 表。其定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef objc_object ** weak_referrer_t;
struct weak_entry_t {
DisguisedPtrobjc_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 {
// out_of_line=0 is LSB of one of these (don't care which)
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
}
}

其中 referent 是被引用的对象,即示例代码中的 newObj 对象。下面的 union 即存储了所有指向该对象的弱引用。由注释可以看到,当out_of_line等于0时,hash表被一个数组所代替。另外,所有的弱引用对象的地址都是存储在weak_referrer_t指针的地址中。

3.释放

weak 指针指向的对象的引用计数=0时,触发对象的 dealloc 函数,析构的详细流程如下:

  • 3.1.调用_objc_rootDealloc 函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- (void)dealloc {
_objc_rootDealloc(self);
}

void _objc_rootDealloc(id obj)
{
assert(obj);
obj->rootDealloc();
}

inline void objc_object::rootDealloc()
{
if (isTaggedPointer()) return;

if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}

rootDealloc中会判断是否有弱引用表和 SideTable 等,没有就通过free函数清除对象内存;有就继续调用 object_dispose 函数:

  • 3.2.调用 objc_destructInstance 函数并最终释放对象内存:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
id object_dispose(id obj) 
{
if (!obj) return nil;

objc_destructInstance(obj);
free(obj);
return nil;
}

void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();

// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}

这里,object_cxxDestruct 是用来释放对象的实例变量;_object_remove_assocations用来移除所有关联的属性;

  • 3.3.调用 objc_clear_deallocating 函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
void objc_clear_deallocating(id obj) 
{
......
SideTable *table = SideTable::tableForPointer(obj);
// clear any weak table items
// clear extra retain count and deallocating bit
// (fixme warn or abort if extra retain count == 0 ?)
OSSpinLockLock(&table->slock);
if (seen_weak_refs) {
arr_clear_deallocating(&table->weak_table, obj);
}
......
}

首先取出对象对应的 SideTable 实例,如果这个对象有关联的弱引用,则调用 arr_clear_deallocating 来清除对象的弱引用信息。

  • 3.4.调用 arr_clear_deallocating 函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
PRIVATE_EXTERN void arr_clear_deallocating(weak_table_t *weak_table, id referent) {
{
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
if (entry == NULL) {
......
return;
}
// zero out references
for (int i = 0; i < entry->referrers.num_allocated; ++i) {
id *referrer = entry->referrers.refs[i].referrer;
if (referrer) {
if (*referrer == referent) {
*referrer = nil;
}
else if (*referrer) {
_objc_inform("__weak variable @ %p holds %p instead of %p\n",
referrer, *referrer, referent);
}
}
}
weak_entry_remove_no_lock(weak_table, entry);
weak_table->num_weak_refs--;
}
}

首先找出对象对应的 weak_entry_t 链表,然后挨个将弱引用置为 nil,最后清理对象的记录。

4.后记

一个 weak 引用的处理涉及各种查表、添加与删除操作,还是有一定消耗的。所以如果大量使用 weak 变量的话,会对性能造成一定的影响。那么,我们应该在什么时候去使用 weak 呢?《Objective-C高级编程》给我们的建议是只在避免循环引用的时候使用 __weak 修饰符。


相关参考:

#©CocoaChina
#©初心丶可曾记-dealloc


weak 的实现
https://davidlii.cn/2018/03/27/weak.html
作者
Davidli
发布于
2018年3月27日
许可协议