這裏假設,此對象不是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;
}
複製代碼
打印結果以下
針對結果,每次操做weak指向的對象都會對該對象進行一次retain和release,retain是在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
用來標記是否使用內聯數組。