weak指針的線程安全和自動置nil的深度探討

前言:

請思考兩個問題竹捉。
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)仑嗅。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末刑巧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子无畔,更是在濱河造成了極大的恐慌啊楚,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件浑彰,死亡現(xiàn)場離奇詭異恭理,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)郭变,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進(jìn)店門颜价,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人诉濒,你說我怎么就攤上這事周伦。” “怎么了未荒?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵专挪,是天一觀的道長。 經(jīng)常有香客問我,道長寨腔,這世上最難降的妖魔是什么速侈? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮迫卢,結(jié)果婚禮上倚搬,老公的妹妹穿的比我還像新娘。我一直安慰自己乾蛤,他們只是感情好每界,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著家卖,像睡著了一般盆犁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上篡九,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天谐岁,我揣著相機(jī)與錄音,去河邊找鬼榛臼。 笑死伊佃,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的沛善。 我是一名探鬼主播航揉,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼金刁!你這毒婦竟也來了帅涂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤尤蛮,失蹤者是張志新(化名)和其女友劉穎媳友,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體产捞,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡醇锚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了坯临。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片焊唬。...
    茶點(diǎn)故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖看靠,靈堂內(nèi)的尸體忽然破棺而出赶促,到底是詐尸還是另有隱情,我是刑警寧澤挟炬,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布鸥滨,位于F島的核電站嗦哆,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏爵赵。R本人自食惡果不足惜吝秕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一泊脐、第九天 我趴在偏房一處隱蔽的房頂上張望空幻。 院中可真熱鬧,春花似錦容客、人聲如沸秕铛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽但两。三九已至,卻和暖如春供置,著一層夾襖步出監(jiān)牢的瞬間谨湘,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工芥丧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留紧阔,地道東北人。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓续担,卻偏偏與公主長得像擅耽,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子物遇,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內(nèi)容