iOS|iOS 中 __weak底层原理

我们在平时的开发过程中,经常会使用到__weak关键字来解决循环引用的问题,被__weak修饰的指针就变成了弱指针,当这个弱指针指向的对象销毁时,会自动将这个弱指针的值置为nil,那么它的底层实现原理又是怎样尼?
我们先来了解下弱引用的两个常见关键字:

  • __weak
  • __unsafe_unretained
接下来我们创建一个新的工程,然后创建一个Person类,来研究下强引用和弱引用的区别,示例代码如下:
Person
@interface Person : NSObject- (void)run; @end@implementation Person- (void)run { NSLog(@"%s", __func__); }// 当person对象销毁时,需要找到指向这个person对象的所有弱指针,将这些弱指针全部清空 - (void)dealloc { NSLog(@"%s", __func__); } @end

main.m
void __strongTest() { // 强指针person1 // 注意:当我们不写__strong时,系统默认就是__strong修饰,这里为了加强对比才写上__strong __strong typeof(Person) *person1; NSLog(@"111"); { Person *person = [[Person alloc] init]; // person1强指针强引用着person对象 person1 = person; NSLog(@"222"); }NSLog(@"%@", person1); [person1 run]; NSLog(@"333"); }void __weakTest() { // 弱指针person2 __weak typeof(Person) *person2; NSLog(@"111"); { Person *person = [[Person alloc] init]; // person2弱指针弱引用着person对象 person2 = person; NSLog(@"222"); }NSLog(@"%@", person2); [person2 run]; NSLog(@"333"); }void __unsafe_unretainedTest() { // 弱指针person3 __unsafe_unretained typeof(Person) *person3; NSLog(@"111"); { Person *person = [[Person alloc] init]; // person3弱指针弱引用着person对象 person3 = person; NSLog(@"222"); }NSLog(@"%@", person3); // Thread 1: EXC_BAD_ACCESS (code=1, address=0x101803f0) [person3 run]; NSLog(@"333"); }int main(int argc, const char * argv[]) { @autoreleasepool { __strongTest(); __weakTest(); __unsafe_unretainedTest(); }NSLog(@"444"); return 0; }

上面示例代码中,我们创建了三个函数__strongTest__weakTest__unsafe_unretainedTest,我们逐一的来分析下person对象的销毁时机
首先我们执行__strongTest函数,我们通过打印可以看到person对象是在NSLog(@"333"); NSLog(@"444"); 之间释放的,这个很好理解。因为person1指针是__strong修饰的,是个强指针,person1强指针强引用着person对象,所以person对象只有出了@autoreleasepool{}大括号的作用域才销毁,如果没有person1指针强引用,那么person对象在执行完NSLog(@"222"); 后便就释放了
我们再来执行__weakTest函数,我们通过打印可以发现,person对象在执行完NSLog(@"222"); 就释放了,并且打印的person2指针为null,执行[person2 run]; 也并没有打印run方法的信息
这是因为person2指针为弱指针,对person对象产生的是弱引用,所以执行完NSLog(@"222"); person对象就销毁了,指向对象的弱指针preson2也就置为nil
我们再来执行__unsafe_unretainedTest函数,我们通过打印可以看出,person对象在执行完NSLog(@"222"); 后就释放了,并且打印person3指针还有值,但是执行[person3 run]; 时,我们发现程序抛出异常Thread 1: EXC_BAD_ACCESS (code=1, address=0x101803f0)
这是因为person3指针也是弱指针,并且是__unsafe_unretained关键字修饰的弱指针,person3弱指针对person对象产生的是弱引用,所以person对象当离开大括号的作用域就销毁了。虽然person对象销毁了,但是person3指针任然有值,指向着person对象已经销毁的内存地址,所以当执行[person3 run]; 语句时,程序就抛出异常,报坏内存访问
从上面的三个函数的执行结果我们可以得出结论:
__weak__unsafe_unretained这两个关键字都能产生弱引用,但是它们又有以下不同:
【iOS|iOS 中 __weak底层原理】__weak产生的弱引用,当弱指针指向的对象销毁时,也会将这个弱指针的值置为nil
__unsafe_unretained产生的弱引用,当弱指针指向的对象销毁时,并不能将这个弱指针的值置为nil,这样就容易造成坏内存访问的异常
所以在平时的开发过程中,我们优先选择使用__weak关键字来实现弱引用
上面总结到使用__weak可以在指向的对象销毁时,会将弱指针的值置为nil,接下来我们通过底层源码来加以验证
底层源码的跟踪路径:objc4源码 -> NSObject.mm -> dealloc() -> _objc_rootDealloc() -> rootDealloc() -> object_dispose() -> objc_destructInstance() -> clearDeallocating() -> clearDeallocating_slow() -> weak_clear_no_lock()
下面对源码流程的核心流程进行些分析讲解:
我们都知道当一个对象即将要销毁时,就会调用这个类的dealloc函数来销毁对象,objc底层源码中的dealloc函数如下:
// Replaced by NSZombies - (void)dealloc {// self为当前调用`dealloc`函数的对象,也就是待销毁的对象 _objc_rootDealloc(self); }

接着执行_objc_rootDealloc(self); 函数,源码如下:
void _objc_rootDealloc(id obj) { assert(obj); obj->rootDealloc(); }

执行obj->rootDealloc()函数,源码如下:
inline void objc_object::rootDealloc() { if (isTaggedPointer()) return; // fixme necessary?// 判断是否为普通isa指针,是否有弱引用等判断 if (fastpath(isa.nonpointer&& !isa.weakly_referenced&& !isa.has_assoc&& !isa.has_cxx_dtor&& !isa.has_sidetable_rc)) {assert(!sidetable_present()); // 如果是`nonpointer`,或者没有`weakly_referenced`等,释放的更快 free(this); } else { // 此时的`this`为`person`对象,也就是待释放的对象 object_dispose((id)this); } }

rootDealloc()函数中,我们看到有一些isa.nonpointerisa.weakly_referenced等,这些都是优化过的isa指针中存储的信息
优化过的isa结构如图:
iOS|iOS 中 __weak底层原理
文章图片
image nonpointerhas_assoc等结构体成员对应的解释如图:
iOS|iOS 中 __weak底层原理
文章图片
image 接着执行object_dispose((id)this); 函数,源码如下:
id object_dispose(id obj) { if (!obj) return nil; // 在释放对象前,做一些释放前的清理工作,例如弱引用指针的清空操作 objc_destructInstance(obj); // 释放对象 free(obj); return nil; }

object_dispose()函数中,我们可以看到,在执行free(obj); 释放对象前,还会执行objc_destructInstance(obj); 来做一些释放前的准备工作,清除弱指针就是在这准备工作中完成的
接下来执行objc_destructInstance(obj); 函数,源码如下:
/*********************************************************************** * objc_destructInstance * Destroys an instance without freeing memory. * Calls C++ destructors. * Calls ARC ivar cleanup. * Removes associative references. * Returns `obj`. Does nothing if `obj` is 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); // 将指向当前对象的弱指针置为nil obj->clearDeallocating(); }return obj; }

接下来调用obj->clearDeallocating(); 函数,源码如下:
inline void objc_object::clearDeallocating() { if (slowpath(!isa.nonpointer)) { // 普通的isa指针 // Slow path for raw pointer isa. sidetable_clearDeallocating(); } else if (slowpath(isa.weakly_referenced||isa.has_sidetable_rc)) { // 存在弱引用或者has_sidetable_rc中存储有引用计数// Slow path for non-pointer isa with weak refs and/or side table data.// 清空弱指针 clearDeallocating_slow(); }assert(!sidetable_present()); }

这里我们需要注意:isa.has_sidetable_rc,当has_sidetable_rc值为1时,这时就说明isa指针中存储不下引用计数了,引用计数需要存储在Sidetable结构体中
接下来执行clearDeallocating_slow(); 函数,源码如下:
// Slow path of clearDeallocating() // for objects with nonpointer isa // that were ever weakly referenced // or whose retain count ever overflowed to the side table. NEVER_INLINE void objc_object::clearDeallocating_slow() { assert(isa.nonpointer&&(isa.weakly_referenced || isa.has_sidetable_rc)); // 通过`[this]`找到`table`,`[this]`:对象的内存地址 SideTable& table = SideTables()[this]; // 加锁操作 table.lock(); // 判断是否被弱引用指引用过 if (isa.weakly_referenced) { // table.weak_table:取出weak_table(全局弱引用表) weak_clear_no_lock(&table.weak_table, (id)this); }// 如果引用计数存储在`SideTable`中 if (isa.has_sidetable_rc) { // 将这个对象的引用计数 从存储引用计数的表中移除掉 table.refcnts.erase(this); }// 解锁操作 table.unlock(); }

这里的SideTable结构体就是用来存储引用计数的底层结构
接下来执行weak_clear_no_lock(&table.weak_table, (id)this); 函数,源码如下:
/** * !!!清除弱指针的核心函数 * * Called by dealloc; nils out all weak pointers that point to the * provided object so that they can no longer be used. * * @param weak_table * @param referent The object being deallocated. */ void weak_clear_no_lock(weak_table_t *weak_table, id referent_id) { // referent_id:待释放的对象// 将referent_id强制转换为`(objc_object *)`类型 objc_object *referent = (objc_object *)referent_id; // 通过对象的内存地址在全局弱引用表中找到这个对象的弱引用表(entry),这个表中存放的都是指向这个对象的弱指针 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; }// weak_referrer_t结构体官方解释:The address of a __weak variable.// 变量referrers:即为弱指针内存地址的集合 // zero out references weak_referrer_t *referrers; // 这个对象对应的弱引用表的大小 size_t count; if (entry->out_of_line()) { // 取出referrers referrers = entry->referrers; // 获取对象所对应的弱引用表的长度 count = TABLE_SIZE(entry); } else { // 取出referrers referrers = entry->inline_referrers; count = WEAK_INLINE_COUNT; }// 遍历这个对象所对应的弱引用表 for (size_t i = 0; i < count; ++i) { // referrers:弱指针的集合 // `*referrer`:为弱引用指针的内存地址 objc_object **referrer = referrers[i]; // 如果内存地址有值 if (referrer) { if (*referrer == referent) { // 将弱引用指针的值赋值为nil,也就是说__weak修饰的对象释放时,将弱指针置为nil就是在此完成的 *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(); } } }// 将这个对象所对应的弱引用表(entry)从全局弱引用表(weak_table)中移除掉 weak_entry_remove(weak_table, entry); }

最终一个对象销毁时,将这个对象的所有弱指针的值置为nil的操作就是下面这段代码:
// 遍历这个对象所对应的弱引用表 for (size_t i = 0; i < count; ++i) { // referrers:弱指针的集合 // `*referrer`:为弱引用指针的内存地址 objc_object **referrer = referrers[i]; // 如果内存地址有值 if (referrer) { if (*referrer == referent) { // 将弱引用指针的值赋值为nil,也就是说__weak修饰的对象释放时,将弱指针置为nil就是在此完成的 *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_clear_no_lock(&table.weak_table, (id)this); 函数中,还有一个很核心的函数调用,就是weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
这个函数的作用就是在全局弱引用表中,通过对象的内存地址作为key,然后用这个key & weak_table->mask得到一个哈希表的索引值,通过这个索引值在全局弱引用表中(哈希表)找到这个对象的弱引用表
weak_entry_for_referent(weak_table, referent)函数,源码如下:
/** * Return the weak reference table entry for the given referent. * If there is no entry for referent, return NULL. * Performs a lookup. * * @param weak_table * @param referent The object. Must not be nil. * * 返回这个对象的弱引用表 * @return The table of weak referrers to this object. */ static weak_entry_t * weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent) { assert(referent); // referent为当前对象// 指向对象的所有weak指针的集合 weak_entry_t *weak_entries = weak_table->weak_entries; if (!weak_entries) return nil; // 根据对象的内存址值 & mask 找到一个索引值,也就是说对象的内存地址作为key size_t begin = hash_pointer(referent) & weak_table->mask; // 哈希表中的索引值 size_t index = begin; size_t hash_displacement = 0; while (weak_table->weak_entries[index].referent != referent) { index = (index+1) & weak_table->mask; if (index == begin) bad_weak_table(weak_table->weak_entries); hash_displacement++; if (hash_displacement > weak_table->max_hash_displacement) { return nil; } }// 通过索引值index,在全局弱引用表中找到对应的value,这个value就是一个对象的弱引用表 return &weak_table->weak_entries[index]; }

从上面的源码中我们可以看到,源码size_t begin = hash_pointer(referent) & weak_table->mask; &weak_table->weak_entries[index];
就是通过hash_pointer(referent)内存地址 & weak_table->mask得到一个哈希表的索引值,然在通过这个索引值就能找到哈希表中对应的value,而这个value就是一个对象对应的弱引用表
weak_clear_no_lock(&table.weak_table, (id)this); 函数中,当我们循环遍历清除了这个对象的所有弱指针后,还执行了weak_entry_remove(weak_table, entry); 函数,源码如下:
/** * 从全局弱引用表中移除这个对象所对应的弱引用表 * Remove entry from the zone's table of weak references. */ static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry) { // remove entry if (entry->out_of_line()) free(entry->referrers); bzero(entry, sizeof(*entry)); // 全局弱引用表的长度 - 1 weak_table->num_entries--; weak_compact_maybe(weak_table); }

这个函数作用是将这个对象所对应的弱引用表(entry)从全局弱引用表(weak_table)中移除掉
在这个清除弱指针的过程中,有以下几个结构我需要注意:
  • SideTable
  • weak_table_t
  • weak_entry_t
  • weak_referrer_t
下面我们在来看看这几个结构体的成员以及对核心成员的解释:
SideTable
// 当isa中的`has_sidetable_rc`值为1时,说明引用计数是存储在SideTable结构体中 struct SideTable {// os_unfair_lock锁 spinlock_t slock; // 存储引用计数值的表,哈希表数据结构 RefcountMap refcnts; // 全局弱引用表,哈希表数据结构 weak_table_t weak_table; SideTable() { memset(&weak_table, 0, sizeof(weak_table)); }~SideTable() { _objc_fatal("Do not delete SideTable."); }void lock() { slock.lock(); } void unlock() { slock.unlock(); } void forceReset() { slock.forceReset(); }// Address-ordered lock discipline for a pair of side tables.template static void lockTwo(SideTable *lock1, SideTable *lock2); template static void unlockTwo(SideTable *lock1, SideTable *lock2); };

weak_table_t
/** * weak_table_t为全局弱引用表结构(哈希表数据结构),对象的内存地址作为key,weak_entry_t为value * * The global weak references table. Stores object ids as keys, * and weak_entry_t structs as their values. */ struct weak_table_t { // weak_entries:weak_table_t结构中的全局弱引用表,也是所有的`weak_entry_t`单元的集合 weak_entry_t *weak_entries; size_tnum_entries; // 全局弱引用表中存储的多少个对象的弱引用表的个数 uintptr_t mask; // 对象的内存地址 & mask 得出一个哈希表的索引 uintptr_t max_hash_displacement; };

weak_entry_t
// 某一个对象的所有弱指针的集合 struct weak_entry_t {DisguisedPtr referent; // 共用体 union { struct {// The address of a __weak variable. // weak_referrer_t:官方解释:弱指针的内存地址// referrers:为某一个对象的所有弱指针地址的集合 weak_referrer_t *referrers; uintptr_tout_of_line_ness : 2; uintptr_tnum_refs : PTR_MINUS_2; // 这个mask即为一个对象的弱引用表中存储的弱指针的个数 - 1,这个和方法缓存中的缓存列表的长度逻辑一样 uintptr_tmask; uintptr_tmax_hash_displacement; }; struct { // out_of_line_ness field is low bits of inline_referrers[1] weak_referrer_tinline_referrers[WEAK_INLINE_COUNT]; }; }; bool out_of_line() { return (out_of_line_ness == REFERRERS_OUT_OF_LINE); }weak_entry_t& operator=(const weak_entry_t& other) { memcpy(this, &other, sizeof(other)); return *this; }weak_entry_t(objc_object *newReferent, objc_object **newReferrer) : referent(newReferent) { inline_referrers[0] = newReferrer; for (int i = 1; i < WEAK_INLINE_COUNT; i++) { inline_referrers[i] = nil; } } };

weak_referrer_t
// The address of a __weak variable. // These pointers are stored disguised so memory analysis tools // don't see lots of interior pointers from the weak table into objects. typedef DisguisedPtr weak_referrer_t;

我们对上面的四个结构的连系进行简单的总结:SideTable结构体中包含了weak_table_t,在weak_table_t中又包含了weak_entry_t,在weak_entry_t中又包含weak_referrer_t,它们是一层层的包含关系,关系图如下图:
iOS|iOS 中 __weak底层原理
文章图片
image 到这里我们对__weak底层的源码分析就结束了,从底层源码中流程分析我们可以很清楚的看出__weak的查找弱引用表,和将弱指针置为nil的实现原理
讲解示例Demo地址:https://github.com/guangqiang-liu/10.1-__weak
更多文章
  • ReactNative开源项目OneM(1200+star):https://github.com/guangqiang-liu/OneM:欢迎小伙伴们 star
  • iOS组件化开发实战项目(500+star):https://github.com/guangqiang-liu/iOS-Component-Pro:欢迎小伙伴们 star
  • 主页:包含多篇iOS和RN开发相关的技术文章http://www.jianshu.com/u/023338566ca5 欢迎小伙伴们:多多关注,点赞
  • ReactNative QQ技术交流群(2000人):620792950 欢迎小伙伴进群交流学习
  • iOS QQ技术交流群:678441305 欢迎小伙伴进群交流学习

    推荐阅读