余额不足.

weak到底做了些啥?

字数统计: 2.5k阅读时长: 11 min
2018/10/19 Share

weak到底做了些啥?

入行也好几年了,关于内存管理几乎是面试中必定会问到的问题,内存管理中问的最多的大概就是循环引用和weak的实现了,关于weak怎么将修饰的对象置为nil的,可能几年前的回答就是:

1
runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象指针的地址)数组。

再之后可能就是weak的三部曲:

1
2
3
1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
2、添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

那么上面提及到的函数到底都做了些啥?

objc_initWeak

1
2
3
4
5
6
7
8
9
10
11
enum HaveOld { DontHaveOld = false, DoHaveOld = true };
enum HaveNew { DontHaveNew = false, DoHaveNew = true };

id objc_initWeak(id *location, id newObj) {
if (!newObj) {
*location = nil;
return nil;
}

return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>(location, (objc_object*)newObj);
}

从上面的代码块,我们可以知道objc_initWeak其实是objc_storeWeak的入口,他主要判断了修饰的对象是否有效,倘若是个无效对象,则直接将指针置为nil,返回nil。

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
//  更新weak变量
// 如果HaveOld是true,则该变量目前是有值的,反之则需要被清空,这个值可能会是nil
// 如果HaveNew是true,则改变量会被分配上一个新的值,这个值可能会是nil
// 如果crashIfDeallocating是true,则newObj正在deallocating的过程中或者newObj不支持若引用,该过程被停止
// 如果crashIfDeallocating是false,用nil存储
enum CrashIfDeallocating {
DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};
template <HaveOld haveOld, HaveNew haveNew, CrashIfDeallocating crashIfDeallocating>
static id storeWeak(id *location, objc_object *newObj) {
assert(haveOld || haveNew);
if (!haveNew) assert(newObj == nil);

// 声明预先初始化类
Class previouslyInitializedClass = nil;
// 声明旧值
id oldObj;
// 声明新旧SideTable
SideTable *oldTable;
SideTable *newTable;

// 分别为新值旧值加锁
retry:
if (haveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (haveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}

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

// 上面代码已将*location赋值给oldObj,两值应该保持一致,如果不相同,则证明oldObj可能在其他线程中被修改
if (haveOld && *location != oldObj) {
// 解锁,然后回到retry重新操作
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
goto retry;
}

// 防止弱引用间的死锁
// 通过 +initialize 保证所有弱引用的 isa 指向非空
if (haveNew && newObj) {
Class cls = newObj->getIsa();
// 判断isa指针不指向previouslyInitializedClass且已经初始化 (previouslyInitializedClass可能指向nil,也可能指向前一个cls的地址)
if (cls != previouslyInitializedClass && !((objc_class *)cls)->isInitialized()) {
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
// _class_getNonMetaClass 通过类或者元类,返回一个普通的类
// _class_initialize 会对没有初始化的类进行 +initialize
_class_initialize(_class_getNonMetaClass(cls, (id)newObj));

// 如果该类已经完成了+initialize,那自然是最好的
// 如果该类依然在执行+initialize中,则可能出现它觉得自己没有意识到自己在调用+initialize的过程中再次调用+initialize的可能性,我们需要对其添加保护策略
// 对previouslyInitializedClass指针进行标记然后重试
previouslyInitializedClass = cls;

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);
// 如果弱引用存储被拒,weak_register_no_lock会返回nil

// 引用计数表中标记为弱引用
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}

// 不要在别的地方设置location,这里会设置他
*location = (id)newObj;
}

SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

return (id)newObj;
}

从以上代码可以看出storeWeak先是做了一系列的条件判断以及保护操作,之后对新值旧值进行了操作,简而言之就是清除旧值分配新值。

但是,这个SideTable是干啥的?

1
2
3
4
5
6
7
8
struct SideTable {
// 自旋锁
spinlock_t slock;
// 引用计数表,为了防止内存泄漏,RefcountMap会隐藏它的指针
RefcountMap refcnts;
// 全局弱引用表
weak_table_t weak_table;
}

这个结构体包含了引用计数表和weak表,想必便是用于管理对象的引用计数和weak表的。

weak_unregister_no_lock

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
/** 
* 解除已经注册的弱引用
* 将在引用对用依然存在,引用地址即将被移除的时候调用
* 如果当前引用地址/引用对象不是一个活跃的弱引用,则什么都不做
* 如果是0引用,则什么都不做
*
* @param weak_table 全局弱引用表.
* @param referent 引用对象.
* @param referrer 引用地址.
*/
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);
}
}
// 不要设置 *referrer = nil, objc_storeWeak() 需要改变这个值
}

storeWeak中清除旧值的操作调用了weak_unregister_no_lock(),该函数最后会调用weak_entry_remove()将其从弱引用表中移除。
该函数首先判断了弱引用条目是否存在,然后判断对象是否处于活跃状态以及引用地址是否还存在,如果处于活跃状态或引用地址依然存在则不做任何操作,否则将该弱引用条目从弱引用表中清除。

weak_register_no_lock

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/**
* 注册一对新的键值对(对象,弱引用指针),如果该对象还存在就创建一个新的弱引用对象入口
*
* @param weak_table 全局弱引用表
* @param referent 弱引用对象
* @param referrer 弱引用指针地址
*/
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;

// 确保引用对象是否有效
bool deallocating;
// hasCustomRR()判断是否有使用自定义的retain/release/autorelease(自定义内存管理)等方法
if (!referent->ISA()->hasCustomRR()) {
// rootIsDeallocating 对象是否正在销毁
deallocating = referent->rootIsDeallocating();
} else {
// SEL_allowsWeakReference 对象是否允许弱引用
BOOL (*allowsWeakReference)(objc_object *, SEL) = (BOOL(*)(objc_object *, SEL))
object_getMethodImplementation((id)referent, SEL_allowsWeakReference);
// 判断referent弱引用方法是否被转发
if ((IMP)allowsWeakReference == _objc_msgForward) {
return nil;
}
deallocating = ! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
}

// 如果deallocating为true,处理一哈
if (deallocating) {
if (crashIfDeallocating) {
_objc_fatal("Cannot form weak reference to instance (%p) of "
"class %s. It is possible that this object was "
"over-released, or is in the process of deallocation.",
(void*)referent, object_getClassName((id)referent));
} else {
return nil;
}
}

// 将对象存储,并记录地址
weak_entry_t *entry;
// 判断弱引用表是否存在referent
if ((entry = weak_entry_for_referent(weak_table, referent))) {
// 为弱引用指针添加至weak_entry_t
append_referrer(entry, referrer);
} else {
// 如果不存在则新建一个weak_entry_t
weak_entry_t new_entry(referent, referrer);
// 如果weak_table表满了,就扩大控件
// 当 weak_table 里的弱引用到达容量3/4时,会将容量拓展为两倍
weak_grow_maybe(weak_table);
// 将弱引用插入表中
weak_entry_insert(weak_table, &new_entry);
}

// 不要设置 *referrer = nil,因为 objc_storeWeak() 函数会需要该指针

return referent_id;
}

该方法先是判断引用是否有效,引用对象是否处于销毁中的状态,最后再判断弱引用表是否存在referent,存在则直接添加弱引用,不存在则先创建weak_entry_t,然后对表进行扩容(如果需要的话),最后将弱引用插入表中。不得不说源码层面的条件判断非常严谨,各个会出现异常位置都有一个条件判断。

以上就是weak分配值的一系列操作了,还有一个问题就是对象调用dealloc是怎么将弱引用变量置为nil的?

自动置为nil

ok,我们都知道当一个对象引用计数为0的时候会调用dealloc方法,那么以dealloc为入口,去看看究竟都发生了一些什么事情。

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
- (void)dealloc {
_objc_rootDealloc(self);
}

void
_objc_rootDealloc(id obj)
{
assert(obj);

obj->rootDealloc();
}

inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?

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);
}
}

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;

}

调用流程
dealloc -> rootDealloc -> object_dispose -> objc_destructInstance -> clearDeallocating

1
2
3
4
5
6
7
8
9
10
11
12
13
14
inline void 
objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}

assert(!sidetable_present());
}

clearDeallocating中的判断表示弱引用会进入clearDeallocating_slow()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));

SideTable& table = SideTables()[this];
table.lock();
if (isa.weakly_referenced) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
if (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
table.unlock();
}

最终弱引用会调用到weak_clear_no_lock()

weak_clear_no_lock

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
44
45
46
47
48
49
50
51
52
53
54
/**
* 被dealnoc调用;清除弱引用指针
*
* @param weak_table 弱引用表
* @param referent 将要被销毁的对象
*/
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
objc_object *referent = (objc_object *)referent_id;

// 先看看是不是没有弱引用
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
if (entry == nil) {
/// XXX shouldn't happen, but does with mismatched CF/objc
//printf("XXX no entry for clear deallocating %p\n", referent);
return;
}

// zero out references
weak_referrer_t *referrers;
size_t count;

// 获取引用位置以及个数
if (entry->out_of_line()) {
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}

// 遍历弱引用数组,置nil
for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[i];
if (referrer) {
if (*referrer == referent) {
*referrer = nil;
}
else if (*referrer) {
_objc_inform("__weak variable at %p holds %p instead of %p. "
"This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
referrer, (void*)*referrer, (void*)referent);
objc_weak_error();
}
}
}

// 最后清除弱引用条目
weak_entry_remove(weak_table, entry);
}

该方法获取了对象的所有弱引用变量,然后遍历弱引用数组,将这些变量分别置为nil。

CATALOG
  1. 1. weak到底做了些啥?
    1. 1.1. objc_initWeak
    2. 1.2. storeWeak
    3. 1.3. weak_unregister_no_lock
    4. 1.4. weak_register_no_lock
    5. 1.5. 自动置为nil
      1. 1.5.1. weak_clear_no_lock