前言:
請思考兩個問題竹捉。
1. weak指針置為nil是線程安全的嗎?問詳細(xì)點(diǎn)就是:當(dāng)一個對象正在delloc時,如果在另一個線程獲取了weak指針榜聂,這時獲取weak怎么保證線程安全焰情?
2.weak指針會自動置為nil的原因就是在一個對象的delloc中會去弱引用表里面查找所存儲weak指針的數(shù)組陌凳,然后去遍歷置為nil。相信這個結(jié)論家都比較認(rèn)同内舟。但是合敦,如果一個類重寫delloc方法,且設(shè)置為MRC并不調(diào)用super delloc验游。也就是說這個類必定不能順利的完成delloc蛤肌,并不能把指針置為nil,但是當(dāng)獲取weak指針的時候批狱,weak指針卻神奇地為nil裸准。難道之前的結(jié)論是錯誤的?
第二個問題是由這篇博客最后遺留下的問題赔硫,很感謝博主這樣的優(yōu)質(zhì)文章炒俱,第一個問題是之前有朋友問過我,我在查找第二個問題時發(fā)現(xiàn)的爪膊。
先上測試代碼权悟。YYEobject繼承NSObject,重寫delloc不調(diào)用super推盛,一定要設(shè)置YYEobject為MRC峦阁,故意發(fā)生內(nèi)存泄漏
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
__weak YYEobject* weakObj = nil;
@autoreleasepool{
YYEobject *obj = [[YYEobject alloc] init];
weakObj = obj;
}
NSLog(@"weakObj:%@", weakObj);//斷點(diǎn)打在這
}
按正常思路分析,YYEobject由于沒有調(diào)用super耘成,所以不能有效的清除弱指針榔昔,發(fā)生內(nèi)存泄漏,所以瘪菌,應(yīng)該能打印出weakObj撒会,但是打印結(jié)果卻是weakObj:(null)。結(jié)果不按套路出牌师妙,這里不應(yīng)該置nil才對的诵肛。
首先看一下匯編代碼,略懂即可默穴,不懂也能看出個大概怔檩。Debug-->Debug workflow -->Always show Disassembli褪秀。
0x1089a2a78 <+184>: callq 0x1089a3184 ; symbol stub for: objc_autoreleasePoolPop
0x1089a2a7d <+189>: jmp 0x1089a2a82 ; <+194> at ViewController.m
0x1089a2a82 <+194>: leaq -0x28(%rbp), %rdi
-> 0x1089a2a86 <+198>: callq 0x1089a31ae ; symbol stub for: objc_loadWeakRetained
0x1089a2a8b <+203>: movq %rax, %rdi
0x1089a2a8e <+206>: leaq 0x28bb(%rip), %rcx ; @"weakObj:%@"
斷點(diǎn)打在打印weak指針地那個一行。通過注釋薛训,可也看出weak指針的獲取是通過objc_loadWeakRetained這個函數(shù)來獲取的溜歪,并非直接獲取weak指針。猜想問題應(yīng)該就出現(xiàn)在這個函數(shù)许蓖,之前也做過一些論證蝴猪,比如給obj關(guān)聯(lián)一個對象,發(fā)現(xiàn)關(guān)聯(lián)對象也沒有被清除膊爪,所以排除了delloc里面清空了弱指針自阱,這里沒有delloc什么事,所以跟以前delloc清空弱指針的邏輯沒有關(guān)系米酬。
鎖定了objc_loadWeakRetained這個函數(shù)沛豌,然后來到runtime源碼,去查看一下該函數(shù)赃额。
/*
Once upon a time we eagerly cleared *location if we saw the object
was deallocating. This confuses code like NSPointerFunctions which
tries to pre-flight the raw storage and assumes if the storage is
zero then the weak system is done interfering. That is false: the
weak system is still going to check and clear the storage later.
This can cause objc_weak_error complaints and crashes.
So we now don't touch the storage until deallocation completes.
*/
id
objc_loadWeakRetained(id *location)
{
id obj;
id result;
Class cls;
SideTable *table;
retry:
// fixme std::atomic this load
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()) {
// Fast case. We know +initialize is complete because
// default-RR can never be set before then.
assert(cls->isInitialized());
if (! obj->rootTryRetain()) {
result = nil;
}
}
else {
// Slow case. We must check for +initialize and call it outside
// the lock if necessary in order to avoid deadlocks.
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;
}
結(jié)論1:weak置nil是線程安全
相信很多同學(xué)仔細(xì)閱讀這個函數(shù)加派。開頭我提地第一個問題也就有了答案,weak置nil是線程安全的跳芳,因?yàn)樯纸酰看潍@取weak指針通過這個函數(shù),函數(shù)里面如果判斷有值或者是非TaggedPointer指針飞盆,會去弱引用表里面取值娄琉,同時對引用計(jì)數(shù)表枷鎖,保證線程安全吓歇,所以這就是蘋果保證獲取weak線程安全的解決方案孽水。
第二個問題依然存在,前期猜測城看,是弱引用表的weak指針并沒有被置為nil女气,objc_loadWeakRetained函數(shù)里面有幾處會直接返回nil,但是才疏學(xué)淺测柠,我無法確定具體哪返回地nil炼鞠。這是還要回到匯編代碼,結(jié)合LLDB動態(tài)調(diào)試鹃愤,希望能抓到蛛絲馬跡簇搅,發(fā)現(xiàn)走了retainWeakReference這個函數(shù)完域。結(jié)合源碼也可以看出软吐,如果tryRetain返回為NO,將會直接返回nil對象
LXDZombieSniffer`-[YYEobject retainWeakReference]:
-> 0x102611510 <+0>: pushq %rbp
0x102611511 <+1>: movq %rsp, %rbp
0x102611514 <+4>: movb $0x1, %al
0x102611516 <+6>: movq %rdi, -0x8(%rbp)
0x10261151a <+10>: movq %rsi, -0x10(%rbp)
0x10261151e <+14>: andb $0x1, %al
0x102611520 <+16>: movzbl %al, %eax
0x102611523 <+19>: popq %rbp
0x102611524 <+20>: retq
吟税,retainWeakReference函數(shù)是可以重寫地凹耙,在YYEobject里面重寫retainWeakReference方法姿现,直接返回YES。
然后重新運(yùn)行了代碼肖抱。果然weak指針沒有置為nil备典,也打印出了值。問題得到了定位意述。
weakObj:<YYEobject: 0x6000002338a0>//weak沒有被置為nil提佣,打印正常了
結(jié)論2:獲取weak的指向?yàn)閚il,其真是的弱引用表可能沒有清空荤崇,或者正在被清空拌屏,但我們?nèi)≈祑eak指針地值是nil,始作俑者是objc_loadWeakRetained方法术荤。會直接返回nil給我們使用倚喂,其真正的弱指針還是存在的,還是指向該對象的瓣戚。
在runtime源碼里面追蹤retainWeakReference地實(shí)現(xiàn)端圈,最終來的了objc_object::rootRetain函數(shù),猜想
應(yīng)該是isa指針的是否正在被delloc的位域起了作用子库。如果一個對象
被標(biāo)記為正在被delloc舱权,那么獲取其weak指針會被直接返回nil。與其weak指針的真身無關(guān)仑嗅。