参考地址---------:http://www.cocoachina.com/ios/20170328/18962.html
weak 实现原理的概括
Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址数组,为什么是指针的地址而不是直接是指针的数组?我认为只得到指针没法做后续更改指针的指向,比如置nil,或是内部算法,整改指向都受限。
weak 的实现原理可以概括一下三步:
1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
2、添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
下面将开始详细介绍每一步:
1、初始化时:runtime会调用objc_initWeak函数,objc_initWeak函数会初始化一个新的weak指针指向对象的地址。
示例代码:
1 2 3 4 |
{ NSObject *obj = [[NSObject alloc] init]; id __weak obj1 = obj; } |
当我们初始化一个weak变量时,runtime会调用 NSObject.mm 中的objc_initWeak函数。这个函数在Clang中的声明如下:
1 |
id objc_initWeak(id *object, id value); |
而对于 objc_initWeak() 方法的实现
1 2 3 4 5 6 7 8 9 10 11 12 |
id objc_initWeak(id *location, id newObj) { // 查看对象实例是否有效 // 无效对象直接导致指针释放 if (!newObj) { *location = nil; return nil; } // 这里传递了三个 bool 数值 // 使用 template 进行常量参数传递是为了优化性能 return storeWeakfalse /*old*/ , true /*new*/ , true /*crash*/ > (location, (objc_object*)newObj); } |
可以看出,这个函数仅仅是一个深层函数的调用入口,而一般的入口函数中,都会做一些简单的判断(例如 objc_msgSend 中的缓存判断),这里判断了其指针指向的类对象是否有效,无效直接释放,不再往深层调用函数。否则,object将被注册为一个指向value的__weak对象。而这事应该是objc_storeWeak函数干的。
objc_initWeak、
objc_storeWeak 大致做了如下图相关操作,更新维护两张表,还需要加锁保证多线程一致性,更新新旧哈希表。
2、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
当weak引用指向的对象被释放时,又是如何去处理weak指针的呢?当释放对象时,其基本流程如下:
1、调用objc_release
2、因为对象的引用计数为0,所以执行dealloc
3、在dealloc中,调用了_objc_rootDealloc函数
4、在_objc_rootDealloc中,调用了object_dispose函数
5、调用objc_destructInstance
6、最后调用objc_clear_deallocating
重点看对象被释放时调用的objc_clear_deallocating函数。该函数实现如下:
1 2 3 4 5 6 7 |
void objc_clear_deallocating(id obj) { assert(obj); assert(!UseGC); if (obj->isTaggedPointer()) return ; obj->clearDeallocating(); } |
也就是调用了clearDeallocating,继续追踪可以发现,它最终是使用了迭代器来取weak表的value,然后调用weak_clear_no_lock,然后查找对应的value,将该weak指针置空,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 |
/** * 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) { 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; } 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); } |
objc_clear_deallocating该函数的动作如下:
1、从weak表中获取废弃对象的地址为键值的记录
2、将包含在记录中的所有附有 weak修饰符变量的地址,赋值为nil
3、将weak表中该记录删除
4、从引用计数表中删除废弃对象的地址为键值的记录
看了objc-weak.mm的源码就明白了:其实Weak表是一个hash(哈希)表,然后里面的key是指向对象的地址,Value是Weak指针的地址的数组。