前言
上篇文章讲了散列表中的引用计数表,还有一个弱引用表没有讲。在使用weak进行弱引用时,引用计数表做了什么呢?下面本文将从__weak入手进行分析弱引用表。
Weak原理
-
先来看看如下代码:
NSObject *obj = [NSObject alloc]; NSLog(@"%ld -- %@", CFGetRetainCount((__bridge CFTypeRef)(obj)), obj); __weak typeof(id) wushuang = obj; NSLog(@"%ld -- %@", CFGetRetainCount((__bridge CFTypeRef)(obj)), obj); NSLog(@"%ld -- %@", CFGetRetainCount((__bridge CFTypeRef)(wushuang)), wushuang); NSLog(@"%ld -- %@", CFGetRetainCount((__bridge CFTypeRef)(wushuang)), wushuang); NSLog(@"%ld -- %@", CFGetRetainCount((__bridge CFTypeRef)(obj)), obj);打印结果如下:
对此可能会有疑问,弱引用后到底做了什么?为什么
weak指针的引用计数会是2,wushuang和obj都是指针,为什么obj的引用计数没有变化?带着问题我们去objc4-818.2源码中探索
objc_initWeak
-
查看汇编可以得知,添加
__weak修饰会走到objc_initWeak函数,这个过程是由LLVM来决定的。id objc_initWeak(id *location, id newObj) // location weak指针地址,newObj object对象 { if (!newObj) { *location = nil; return nil; } return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating> (location, (objc_object*)newObj); }代码中
location是weak指针,newObj是NSObject对象,最终会走到storeWeak,通过观察发现objc_destroyWeak也会走入storeWeak方法,只是传入的参数不同 -
storeWeak主要代码如下enum CrashIfDeallocating { DontCrashIfDeallocating = false, DoCrashIfDeallocating = true }; template <HaveOld haveOld, HaveNew haveNew, enum CrashIfDeallocating crashIfDeallocating> static id storeWeak(id *location, objc_object *newObj) { ASSERT(haveOld || haveNew); if (!haveNew) ASSERT(newObj == nil); Class previouslyInitializedClass = nil; id oldObj; SideTable *oldTable; SideTable *newTable; retry: if (haveOld) { ... } else { ... } if (haveNew) { newTable = &SideTables()[newObj]; // 获取对象的散列表 } else { ... } SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable); // 对散列表进行加锁 if (haveOld && *location != oldObj) { ... } // 通过确保没有弱引用对象具有未+初始化的isa,可以防止弱引用机制和+初始化机制之间的死锁 if (haveNew && newObj) { ... } // 销毁时调用方法 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 ? CrashIfDeallocating : ReturnNilIfDeallocating); // weak_register_no_lock returns nil if weak store should be rejected // Set is-weakly-referenced bit in refcount table. if (!newObj->isTaggedPointerOrNil()) { ... } // Do not set *location anywhere else. That would introduce a race. *location = (id)newObj; } else { } SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); // 解锁 callSetWeaklyReferenced((id)newObj); return (id)newObj; }根据代码分析,最终会进入
weak_register_no_lock函数 -
weak_register_no_lock:id weak_register_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions) { objc_object *referent = (objc_object *)referent_id; // referent:newObject, NSObject 对象 objc_object **referrer = (objc_object **)referrer_id; // location指针,也就是weak指针 if (referent->isTaggedPointerOrNil()) return referent_id; // ensure that the referenced object is viable if (deallocatingOptions == ReturnNilIfDeallocating || deallocatingOptions == CrashIfDeallocating) { bool deallocating; if (!referent->ISA()->hasCustomRR()) { deallocating = referent->rootIsDeallocating(); // 返回false } else { ... } if (deallocating) { ... } } // now remember it and where it is being stored weak_entry_t *entry; 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_entry_t weak_grow_maybe(weak_table); // 查看weak_table的size判断是否需要扩容 weak_entry_insert(weak_table, &new_entry); // 插入 } // Do not set *referrer. objc_storeWeak() requires that the // value not change. return referent_id; }- 方法的核心就是根据
referent对象去查找weak_table中的weak_entries链表中的weak_entry_t对象entry:- 如果
entry存在,则将weak指针存入weak_entry_t中的referrers链表中,存储时 需要遵循 3/4 两倍扩容规则。 - 如果
entry不存在,则根据object对象和weak指针创建一个新的entry,再将entry插入weak_table的weak_entries链表中。
- 如果
- 方法的核心就是根据
根据分析得知__weak并没有引起引用计数的变化,那么也就是说weak对象引用计数变化的因素和打印时调用weak对象有关,通过断点汇编分析,打印时代码会走到objc_loadWeak方法
objc_loadWeak
-
代码如下:
id objc_loadWeak(id *location) { if (!*location) return nil; return objc_autorelease(objc_loadWeakRetained(location)); }代码主要是将
objc_loadWeakRetained调用返回的结果,再调用objc_autorelease方法,核心代码就定位到objc_loadWeakRetained -
objc_loadWeakRetained:id objc_loadWeakRetained(id *location) { id obj; id result; Class cls; SideTable *table; retry: // fixme std::atomic this load obj = *location; // 根据weak指针获取object对象 if (obj->isTaggedPointerOrNil()) return obj; table = &SideTables()[obj]; // 根据object对象获取散列表 table->lock(); // 加锁 if (*location != obj) { ... } result = obj; cls = obj->ISA(); if (! cls->hasCustomRR()) { ASSERT(cls->isInitialized()); if (! obj->rootTryRetain()) { // obj引用计数变化 result = nil; } } else { ... } table->unlock(); return result; }- 根据断点走发现,最终代码进入了
rootTryRetain
- 根据断点走发现,最终代码进入了
-
rootTryRetain:ALWAYS_INLINE bool objc_object::rootTryRetain() { return rootRetain(true, RRVariant::Fast) ? true : false; }最终走到了我们熟悉的代码
rootRetain,原来在此处进行引用计数+1。 -
在使用
weak对象时,会短暂的进行引用计数+1,但使用过后会调用objc_release方法进行进行引用计数-1,所以多次调用打印wushuang时,引用计数始终是2
objc_destroyWeak
-
weak对象销毁时会调用objc_destroyWeak,最终代码也会进入storeWeak,通过上面的分析得知,销毁的核心代码是调用weak_unregister_no_lock,核心代码如下:void weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id) { objc_object *referent = (objc_object *)referent_id; // object对象 objc_object **referrer = (objc_object **)referrer_id; // weak指针 weak_entry_t *entry; if (!referent) return; if ((entry = weak_entry_for_referent(weak_table, referent))) { remove_referrer(entry, referrer); // remove 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); // 移除entry } } }这部分代码比较好理解,就是先根据
object对象和弱引用表获取entry,然后删除weak指针,最后再移除entry
弱引用表结构图
-
弱引用表结构图如下: