这里假设,此对象不是TaggedPointer对象,除了一些必要的判断外,在ARC中,获取weak指针时,会调用objc_loadWeakRetained
,此方法最终会调用objc_object::rootRetain
,对该对象的引用计数器加1,而后在此条语句的下面插入一条release语句,对引用计数器减1,在MRC中,会调用objc_autorelease(objc_loadWeakRetained(location));
,利用objc_autorelease对引用计数器减1.c#
最近复习了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指针的真身无关。bash
网上分析objc_loadWeakRetained
源码的文章比较多,这里只贴出来,就不分析了,ide
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;
}
复制代码
对于做者的答案我是认同的,不过我认为做者那么说不全面,我认为的流程是这样的函数
if (! obj->rootTryRetain()) { result = nil; }
,尝试对该对象进行retainobj->rootTryRetain()
,这个方法最终会调用objc_object::rootRetain(bool tryRetain, bool handleOverflow)
,而且参数为true,false,在这个函数里有一下判断:当tryRetain为true,而且对象为被标记为deallocating时,会返回nilpost
if (slowpath(tryRetain && newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
return nil;
}
复制代码
固然做者的那个答案也没有错,是由于做者重写了retainWeakReference
方法,让hasCustomRR为true,会走下面的else,而且做者把retainWeakReference
直接返回了YES,那么最终会返回该对象。只是该对象被标记为deallocating,并无真正的被释放。测试
这个时候我又产生了一个新的疑问,既然获取weak修饰的对象,会调用objc_loadWeakRetained
方法,而此方法最终又会调用rootRetain
对该对象的引用计数器加1,那么何时对该对象的引用计数器减1的呢?ui
建立一个可调试的objc-runtime的工程,在rootRetain
方法中添加打印语句printf("rootRetain\n");
,在rootRelease
方法中添加打印语句printf("rootRelease\n");
,而后测试下面代码this
@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\n");
weakPerson1.age = 18;
printf("第二次retain-release\n");
weakPerson1.age = 18;
printf("第三次retain-release\n");
weakPerson1.age = 18;
printf("第四次retain-release\n4");
NSLog(@"hello world");
}
return 0;
}
复制代码
打印结果以下
objc_loadWeakRetained
中通过层层调用
rootRetain
方法添加的,那release如何调用的呢?
这时候分为2种状况:
weakPerson1.age = 18;
至关于weakPerson1.age = 18;object_release(obj)
objc_loadWeakRetained
,而是会调用objc_loadWeak
,此方法的实现以下:利用自动释放池,对retain的对象进行release操做id objc_loadWeak(id *location) {
if (!*location) return nil;
return objc_autorelease(objc_loadWeakRetained(location));
}
复制代码
在提出问题以前,你首先要了解weak_table_t
和weak_entry_t
以及weak的基本原理。咱们知道weak_entry_t
是一个存放着某个对象全部的弱引用列表,是一个类数组对象。那么下面2种状况,weak_entry_t
的长度分别是多少? 第一种状况:
__weak Person *weakPerson1;
@autoreleasepool {
Person *obj = [[Person alloc]init];
weakPerson1 = obj;
}
// 在此时,对象obj会被释放,在释放的过程会把全部指向它的弱引用都置为nil
// 此时存储弱引用的weak_entry_t的长度是多少
复制代码
第二种状况:
__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,缘由是weak_entry_t
在存储的弱引用的个数小于4的时候,使用的是内联数组,直接初始化了4个位置,也就是说当小于4时,长度会一直是4,每次须要删除某一个弱引用时,都会对数组进行遍历,查找到该引用进行置nil,须要添加时,会遍历此数组,看有没有为nil的,如有,就表明有空位,就赋值,若没有就表明此内联的数组已经满了,此时会把内联数组转成哈希表,哈希表的默认长度为8.weak_entry_t
中out_of_line
用来标记是否使用内联数组。