答案
这里假设,此对象不是TaggedPointer对象,除了一些必要的判断外,在ARC中,获取weak指针时,会调用
为什么我会有这个疑问?
最近复习了OC内存管理的相关知识,在查阅相关知识时,看到了这篇文章weak指针的线程安全和自动置nil的深度探讨,作者提出了一个下面这个比较有意思的问题?
weak指针会自动置为nil的原因就是在一个对象的delloc中会去弱引用表里面查找所存储weak指针的数组,然后去遍历置为nil。相信这个结论家都比较认同。但是,如果一个类重写delloc方法,且设置为MRC并不调用super delloc。也就是说这个类必定不能顺利的完成delloc,并不能把指针置为nil,但是当获取weak指针的时候,weak指针却神奇地为nil。难道之前的结论是错误的?
对于这个问题,作者给出的答案是:获取weak的指向为nil,其真是的弱引用表可能没有清空,或者正在被清空,但我们取值weak指针地值是nil,始作俑者是objc_loadWeakRetained方法。会直接返回nil给我们使用,其真正的弱指针还是存在的,还是指向该对象的。在runtime源码里面追踪retainWeakReference地实现,最终来的了objc_object::rootRetain函数,猜想:应该是isa指针的是否正在被delloc的位域起了作用。如果一个对象被标记为正在被delloc,那么获取其weak指针会被直接返回nil。与其weak指针的真身无关。
网上分析
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 | 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 { if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) { BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL)) class_getMethodImplementation(cls, SEL_retainWeakReference); if ((IMP)tryRetain == _objc_msgForward) { result = nil; } else if (! (*tryRetain)(obj, SEL_retainWeakReference)) { result = nil; } } else { table->unlock(); _class_initialize(cls); goto retry; } } table->unlock(); return result; } |
对于作者的答案我是认同的,不过我认为作者那么说不全面,我认为的流程是这样的
- 获取weak指针时,会调用objc_loadWeakRetained
- 判断weak指向的对象是否是isTaggedPointer,若是:直接返回该对象
- 判断ISA->hasCustomRR(),我与作者的分歧就在着,此比特位会在该类或父类重写下列方法时retain/release/autorelease/retainCount/_tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference返回true,一般情况我们都不会重写这些方法,因此会返回false,取反就为true
- 那么下一步就会执行
if (! obj->rootTryRetain()) { result = nil; } ,尝试对该对象进行retain - 若retain成功则返回该对象
- 若retain失败,则会返回nil,
1 2 3 4 5 | if (slowpath(tryRetain && newisa.deallocating)) { ClearExclusive(&isa.bits); if (!tryRetain && sideTableLocked) sidetable_unlock(); return nil; } |
当然作者的那个答案也没有错,是因为作者重写了
这个时候我又产生了一个新的疑问,既然获取weak修饰的对象,会调用
创建一个可调试的objc-runtime的工程,在
");
");
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 | @interface Person : NSObject @property (nonatomic, assign)NSInteger age; @end @implementation Person @end int main(int argc, const char * argv[]) { @autoreleasepool { __weak Person *weakPerson1; Person *obj = [[Person alloc]init]; weakPerson1 = obj; weakPerson1.age = 18; printf("第一次retain-release "); weakPerson1.age = 18; printf("第二次retain-release "); weakPerson1.age = 18; printf("第三次retain-release "); weakPerson1.age = 18; printf("第四次retain-release 4"); NSLog(@"hello world"); } return 0; } |
打印结果如下
针对结果,每次操作weak指向的对象都会对该对象进行一次retain和release,retain是在
这时候分为2种情况:
- 在ARC中编译器会在weak对象操作的下面插入一条release语句,
weakPerson1.age = 18; 相当于weakPerson1.age = 18;object_release(obj) - 在MRC中,获取weak指向的对象时,并不会直接调用
objc_loadWeakRetained ,而是会调用objc_loadWeak ,此方法的实现如下:利用自动释放池,对retain的对象进行release操作
1 2 3 4 | id objc_loadWeak(id *location) { if (!*location) return nil; return objc_autorelease(objc_loadWeakRetained(location)); } |
关于weak的另一个问题
在提出问题之前,你首先要了解
第一种情况:
1 2 3 4 5 6 7 | __weak Person *weakPerson1; @autoreleasepool { Person *obj = [[Person alloc]init]; weakPerson1 = obj; } // 在此时,对象obj会被释放,在释放的过程会把所有指向它的弱引用都置为nil // 此时存储弱引用的weak_entry_t的长度是多少 |
第二种情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | __weak Person *weakPerson1; __weak Person *weakPerson2; __weak Person *weakPerson3; __weak Person *weakPerson4; __weak Person *weakPerson5; @autoreleasepool { Person *obj = [[Person alloc]init]; weakPerson1 = obj; weakPerson2 = obj; weakPerson3 = obj; weakPerson4 = obj; weakPerson5 = obj; } // 在此时,对象obj会被释放,在释放的过程会把所有指向它的弱引用都置为nil // 此时存储弱引用的weak_entry_t的长度是多少 return 0; |
答案是,第一种情况是4,第二种情况是8,原因是
参考
- 从源码角度看苹果是如何实现 retainCount、retain 和 release 的
- Xcode 10 下如何创建可调试的objc4-723、objc4-750.1工程
- weak指针的线程安全和自动置nil的深度探讨
- 理解 ARC 实现原理