weak 修饰的变量,不会增加所引用对象的引用计数,且会在该对象析构后自动将指向置为nil。其本质是初始化和赋值操作会被编译器改写为 objc_initWeak 或者 objc_storeWeak 函数调用。objc_initWeak 的逻辑只是比 objc_storeWeak 多了所赋值对象的判空,接下来会直接调用 storeWeak(id *location, objc_object *newObj) 函数。

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
34
35
36
37
38
39
40
41
42
43
static id 
storeWeak(id *location, objc_object *newObj)
{
id oldObj;
SideTable *oldTable;
SideTable *newTable;

if (haveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (haveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}

// 加锁
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);

// 将旧的值从链表中清除
if (haveOld) weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);

// Assign new value, if any.
if (haveNew) {
// 将新的值加入链表
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating);

// 在refcount表中设置弱引用位,标记该对象被指针弱引用
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}
*location = (id)newObj;
}

// 释放锁
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
return (id)newObj;
}

查看 storeWeak 函数源码,如果weak指针已经有值,则会清除旧值,然后赋新值。清除旧值和赋值操作分别为 weak_unregister_no_lockweak_register_no_lock 两个函数。

那入参和 SideTable 又是什么?

之前再看 @synchronized 的相关实现时,就已经发现 StripedMap 是个哈希表,而这里有一个全局的 StripedMap,key 是一个对象,value 就是 SideTableSideTable 及其属性的结构:

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
struct SideTable {
spinlock_t slock;
RefcountMap refcnts; // 引用计数表,isa 指针存放引用计数不下时,存放在这里
weak_table_t weak_table;// weak 指针表
};

struct weak_table_t {
weak_entry_t *weak_entries;
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};

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 {
// WEAK_INLINE_COUNT 4
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};

/// 对象包装类型
typedef DisguisedPtr<objc_object *> weak_referrer_t;

SideTable 是对 weak_table_t 的封装,除了持有 weak_table_t 变量,还持有一个自旋锁来保证线程安全。

总结

内存中维护着一个全局的哈希表,以对象的引用为 key,value 是一个SideTableSideTable 有一个自旋锁,有存放引用计数,还有一个存放着 weak 修饰的指针地址数组。

对于 TaggedPointer 对象,并不会设置 isa 中的弱引用标记位