答案
這里假設(shè),此對象不是TaggedPointer對象,除了一些必要的判斷外,在ARC中,獲取weak指針時,會調(diào)用objc_loadWeakRetained,此方法最終會調(diào)用objc_object::rootRetain,對該對象的引用計數(shù)器加1,然后在此條語句的下面插入一條release語句,對引用計數(shù)器減1,在MRC中,會調(diào)用objc_autorelease(objc_loadWeakRetained(location));,利用objc_autorelease對引用計數(shù)器減1.
為什么我會有這個疑問?
最近復(fù)習了OC內(nèi)存管理的相關(guān)知識,在查閱相關(guān)知識時,看到了這篇文章weak指針的線程安全和自動置nil的深度探討,作者提出了一個下面這個比較有意思的問題?
weak指針會自動置為nil的原因就是在一個對象的delloc中會去弱引用表里面查找所存儲weak指針的數(shù)組,然后去遍歷置為nil。相信這個結(jié)論家都比較認同。但是,如果一個類重寫delloc方法,且設(shè)置為MRC并不調(diào)用super delloc。也就是說這個類必定不能順利的完成delloc,并不能把指針置為nil,但是當獲取weak指針的時候,weak指針卻神奇地為nil。難道之前的結(jié)論是錯誤的?
對于這個問題,作者給出的答案是:獲取weak的指向為nil,其真是的弱引用表可能沒有清空,或者正在被清空,但我們?nèi)≈祑eak指針地值是nil,始作俑者是objc_loadWeakRetained方法。會直接返回nil給我們使用,其真正的弱指針還是存在的,還是指向該對象的。在runtime源碼里面追蹤retainWeakReference地實現(xiàn),最終來的了objc_object::rootRetain函數(shù),猜想:應(yīng)該是isa指針的是否正在被delloc的位域起了作用。如果一個對象被標記為正在被delloc,那么獲取其weak指針會被直接返回nil。與其weak指針的真身無關(guān)。
網(wǎng)上分析objc_loadWeakRetained源碼的文章比較多,這里只貼出來,就不分析了,
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指針時,會調(diào)用objc_loadWeakRetained
- 判斷weak指向的對象是否是isTaggedPointer,若是:直接返回該對象
- 判斷ISA->hasCustomRR(),我與作者的分歧就在著,此比特位會在該類或父類重寫下列方法時retain/release/autorelease/retainCount/_tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference返回true,一般情況我們都不會重寫這些方法,因此會返回false,取反就為true
- 那么下一步就會執(zhí)行
if (! obj->rootTryRetain()) { result = nil; },嘗試對該對象進行retain - 若retain成功則返回該對象
- 若retain失敗,則會返回nil,
obj->rootTryRetain(),這個方法最終會調(diào)用objc_object::rootRetain(bool tryRetain, bool handleOverflow),并且參數(shù)為true,false,在這個函數(shù)里有一下判斷:當tryRetain為true,并且對象為被標記為deallocating時,會返回nil
if (slowpath(tryRetain && newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
return nil;
}
當然作者的那個答案也沒有錯,是因為作者重寫了retainWeakReference方法,讓hasCustomRR為true,會走下面的else,并且作者把retainWeakReference直接返回了YES,那么最終會返回該對象。只是該對象被標記為deallocating,并沒有真正的被釋放。
這個時候我又產(chǎn)生了一個新的疑問,既然獲取weak修飾的對象,會調(diào)用objc_loadWeakRetained方法,而此方法最終又會調(diào)用rootRetain對該對象的引用計數(shù)器加1,那么什么時候?qū)υ搶ο蟮囊糜嫈?shù)器減1的呢?
創(chuàng)建一個可調(diào)試的objc-runtime的工程,在rootRetain方法中添加打印語句printf("rootRetain\n");,在rootRelease方法中添加打印語句printf("rootRelease\n");,然后測試下面代碼
@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;
}
打印結(jié)果如下

針對結(jié)果,每次操作weak指向的對象都會對該對象進行一次retain和release,retain是在objc_loadWeakRetained中經(jīng)過層層調(diào)用rootRetain方法添加的,那release如何調(diào)用的呢?
這時候分為2種情況:
- 在ARC中編譯器會在weak對象操作的下面插入一條release語句,
weakPerson1.age = 18;相當于weakPerson1.age = 18;object_release(obj) - 在MRC中,獲取weak指向的對象時,并不會直接調(diào)用
objc_loadWeakRetained,而是會調(diào)用objc_loadWeak,此方法的實現(xiàn)如下:利用自動釋放池,對retain的對象進行release操作
id objc_loadWeak(id *location) {
if (!*location) return nil;
return objc_autorelease(objc_loadWeakRetained(location));
}
關(guān)于weak的另一個問題
在提出問題之前,你首先要了解weak_table_t和weak_entry_t以及weak的基本原理。我們知道weak_entry_t是一個存放著某個對象所有的弱引用列表,是一個類數(shù)組對象。那么下面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在存儲的弱引用的個數(shù)小于4的時候,使用的是內(nèi)聯(lián)數(shù)組,直接初始化了4個位置,也就是說當小于4時,長度會一直是4,每次需要刪除某一個弱引用時,都會對數(shù)組進行遍歷,查找到該引用進行置nil,需要添加時,會遍歷此數(shù)組,看有沒有為nil的,若有,就代表有空位,就賦值,若沒有就代表此內(nèi)聯(lián)的數(shù)組已經(jīng)滿了,此時會把內(nèi)聯(lián)數(shù)組轉(zhuǎn)成哈希表,哈希表的默認長度為8.weak_entry_t中out_of_line用來標記是否使用內(nèi)聯(lián)數(shù)組。