本文解决的问题
weak 的代码实现原理
当对象释放的时候,如何实现将 weak 指针置为 nil
weak 指针的线程安全
阅读本文的前提 1 id __weak weakObj = obj;
obj 在本文中称之为【被引用对象】,即 referent
weakObj 在本文中称之为【弱引用对象】,即 referrer
weak 的代码实现原理 以上例子中的代码的 Clang 实现如下
1 2 id weakObj; objc_initWeak (&weakObj, obj);
objc_initWeak
源码
1 2 3 4 5 id objc_initWeak (id *addr, id val) { *addr = 0 ; if (!val) return nil; return objc_storeWeak (addr, val); }
简化版 objc_storeWeak
源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 id objc_storeWeak(id *location, id newObj) { id oldObj = *location; SideTable *oldTable = SideTable::tableForPointer(oldObj); SideTable *newTable = SideTable::tableForPointer(newObj); weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); newObj = weak_register_no_lock(&newTable->weak_table, newObj, location); *location = newObj; return newObj; }
相关类的简化版源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class SideTable {private : static uint8_t table_buf[SIDE_TABLE_STRIPE * SIDE_TABLE_SIZE]; public : weak_table_t weak_table; RefcountMap refcnts; static SideTable *tableForPointer (const void *p) { uintptr_t a = (uintptr_t )p; int index = ((a >> 4 ) ^ (a >> 9 )) & (SIDE_TABLE_STRIPE - 1 ); return (SideTable *)&table_buf[index * SIDE_TABLE_SIZE]; } };
SideTable *oldTable = SideTable::tableForPointer(oldObj);
可能看完这句代码,我们会错以为 SideTable 和 obj 是一一对应的,其实并不是
SideTable 有一个成员 table_buf
,它是 static 的,全局唯一
由 tableForPointer 的源码我们可以知道,只是根据对象的地址来获取 table_buf
中的其中一张 SideTable
由此可见,SideTable 并不是和 obj 对象一一对应,而是全局有多份,多个对象可能共享同一个 SideTable
另外,可以看到 SideTable 还负责相关对象的引用计数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 struct weak_table_t { weak_entry_t *weak_entries; size_t num_entries; ... }; struct weak_entry_t { DisguisedPtr<objc_object> referent; struct { weak_referrer_t *referrers; ... } }; typedef objc_object ** weak_referrer_t ;
为了更好的理解 weak_table_t
与 obj 之间的对应关系,我们看下 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 id weak_register_no_lock (weak_table_t *weak_table, id referent, id *referrer) { ... weak_entry_t *entry; if ((entry = weak_entry_for_referent (weak_table, referent))) { append_referrer (entry, referrer); } else { weak_entry_t new_entry (referent, referrer); weak_entry_insert (weak_table, &new_entry); } return referent_id; } void weak_unregister_no_lock (weak_table_t *weak_table, id referent, id *referrer) { if (!referent) return ; weak_entry_t *entry; if ((entry = weak_entry_for_referent (weak_table, referent))) { remove_referrer (entry, referrer); ... if (empty) { weak_entry_remove (weak_table, entry); } } }
当对象释放的时候,如何实现将 weak 指针置为 nil 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 - (void )dealloc { _objc_rootDealloc(self ); } void _objc_rootDealloc(id obj) { obj->rootDealloc(); } void objc_object::rootDealloc() { ... 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) { bool cxx = obj->hasCxxDtor(); bool assoc = obj->hasAssociatedObjects(); if (cxx) object_cxxDestruct(obj); if (assoc) _object_remove_assocations(obj); obj->clearDeallocating(); } return obj; } void objc_object::clearDeallocating() { SideTable *table = SideTable::tableForPointer(this ); weak_clear_no_lock(&table->weak_table, (id )this ); } 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); weak_referrer_t *referrers; size_t count; referrers = entry->referrers; count = TABLE_SIZE(entry); for (size_t i = 0 ; i < count; ++i) { objc_object **referrer = referrers[i]; if (referrer) { if (*referrer == referent) { *referrer = nil ; } } } weak_entry_remove(weak_table, entry); }
可以看到对象释放时会调用 dealloc,
一步步调用到了 clearDeallocating,然后调用 tableForPointer 寻找对应的 SideTable,拿到 weak_table_t
最终调用 weak_clear_no_lock
,将所有的 referrer 指向的值(即 weak 指针),置为 nil,并从 weak_table_t
表中删除该对象的 weak_entry_t
通俗解释:
系统会把 weakObj 会放入一个 hash 表中。 用 obj 的内存地址作为 key,当 obj 的引用计数为 0 的时候会执行其 dealloc,此时会在这个 weak 表中搜索,找到所有以 &obj 为 key 的对象,设置为 nil
weak 指针的线程安全 问题:当一个对象正在 delloc 时,如果在另一个线程获取了 weak 指针,这时获取到的 weak 指针有没有可能是野指针?
以下的代码例子模拟了这样一个过程,多个线程正在访问 weakObj,其中一个线程对 self.obj 释放了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 self .obj = [NSObject new];int n = 500 ;while (n--){ __weak NSObject *weakObj = self .obj; dispatch_async (dispatch_get_global_queue(0 , 0 ), ^{ if (n == 480 ) { self .obj = nil ; } int m = 1000 ; while (m--) { NSLog (@"%@----%@" , weakObj, @(n)); } }); }
结论:不会挂,不可能是野指针。weak 的访问是线程安全的
1 2 3 Person *obj = [[Person alloc] init]; id __weak weakObj = obj;NSLog (@"%@" , weakObj);
通过 clang -rewrite-objc MyBlock.c
重写后得到的伪代码
1 2 3 4 5 6 7 id weakObj;objc_initWeak(&weakObj, obj); id tmp = objc_loadWeakRetained(&obj);NSLog (@"%@" , tmp);objc_release(tmp);
当我们访问 weakObj 的时候,编译器会转为 objc_loadWeakRetained
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 id objc_loadWeakRetained(id *location){ id obj; id result; Class cls; SideTable *table; retry: obj = *location; if (!obj) return nil ; if (obj->isTaggedPointer()) return obj; table = &SideTables()[obj]; table->lock(); if (*location != obj) { table->unlock(); goto retry; } result = obj; cls = obj->ISA(); if (! cls->hasCustomRR()) { if (! obj->rootTryRetain()) { result = nil ; } } else { } table->unlock(); return result; }
获取 weak 指针时,会调用 objc_loadWeakRetained
不讨论 isTaggedPointer 这种特殊情况
hasCustomRR 在重写 retain/release/autorelease/retainCount/_tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference 等方法时会返回true,一般情况会返回 false。这里只讨论返回 false 的情况
rootTryRetain 会尝试对该对象进行 retain,里面会判断该对象是否正在 deallocating,如果是则返回 nil
通俗概括以上代码:获取 weak 时调用 objc_loadWeakRetained
,获取过程会加锁。如果该对象已经释放或正在释放则返回 nil,否则对该对象进行 retain 并返回。因此我们得出结论:对 weak 指针的访问是线程安全的
那么问题来了,既然有 retian,那什么时候 release 呢?答案是 ARC 下会在 weak 指针访问完成后,自动插 release 代码,如下
1 2 3 4 id tmp = objc_loadWeakRetained(&obj);NSLog (@"%@" , tmp);objc_release(tmp);
参考