不安全的weak變量

對于存在多線程釋放并且并發(fā)訪問的對象,不建議使用weak修飾或訪問。因為weak的底層實現(xiàn)并不完全是線程安全,否則較容易導(dǎo)致over-release而crash。

一、問題

每次版本升級初期總是有少部分會遇到如下的crash

請在這里填寫圖片描述

雖然量很少,但總是有也很是煩人。沒辦法只能看下到底是怎么回事。

二、問題描述

很明顯,這是一個over-release的問題;掛在objc_release里;

業(yè)務(wù)代碼如下:

- (void)getIconImage:(NSString*)uri
{
    DefineWeakSelfBeforeBlock();
    NSString * fullURLString = uri;
    if ([fullURLString hasPrefix:@"http://"]) {
        fullURLString = [NSString stringWithFormat:@"http:%@", fullURLString];
    }
    NSURL *url = [fullURLString createURL];
    NSString *title=self.title;
    
    [self download:url completion:^(NSData *data, NSError *error) {
        DefineStrongSelfInBlock(sself);
        UIImage *image=nil;
        if(data)
        {
            // 進(jìn)入自繪邏輯
            UIImage * reseponseImage = [UIImage imageWithData:data scale:[UIScreen mainScreen].scale];
            if (reseponseImage) {
                UIColor * color = [reseponseImage getPresentColor];
                image = [UIQuicklinkImageRenderer defaultIconImageWithTitle:title backgroundColor:color];
            }
        }
        if(!image)
        {
            image = DEFAULT_ICON(title);
        }
        [sself doCompletion:image error:error];
    }];
}

crash堆棧不夠直觀,但是可以簡單猜測出最后訪問sself時肯定出現(xiàn)了over-release了;
但是這是標(biāo)準(zhǔn)的ARC代碼,怎么會出現(xiàn)over-release呢?除非編譯器干了什么,或者objc本身出錯了吧?

三、問題分析

這個問題發(fā)生時有一個可能比較多的場景是,多線程多次并發(fā)回調(diào)該函數(shù)時,因此這個問題很快就能找到原因了;

既然單獨看代碼看不出什么問題,那就直接上匯編找找線索;
根據(jù)猜測crash很可能是sself的over-release問題,那為什么這里會觸發(fā)了release了呢?

objcloadWeakRetained

weak的sself訪問,在這里被ARC轉(zhuǎn)換為了objcloadWeakRetained函數(shù),然后又對retain的該指針,進(jìn)行了objc_release,匯編代碼如下:

如上的代碼實際上訪問sself時變成了如下代碼,即訪問weakSelf被ARC變?yōu)榱薿bjc_loadWeakRetained

0000000101cb7bcc         add        x0, sp, #0x8         ;獲取sself                       ; CODE XREF=-[MttQuicklinkCustomIconTask getIconImage:]+688
0000000101cb7bd0         bl         imp___stubs__objc_loadWeakRetained
0000000101cb7bd4         mov        x21, x0
0000000101cb7bd8         adrp       x8, #0x103855000
0000000101cb7bdc         ldr        x1, [x8, #0xd18]
0000000101cb7be0         mov        x2, x23
0000000101cb7be4         mov        x3, x20
0000000101cb7be8         bl         imp___stubs__objc_msgSend
0000000101cb7bec         mov        x0, x21
0000000101cb7bf0         bl         imp___stubs__objc_release  ;release sself
0000000101cb7bf4         mov        x0, x23
0000000101cb7bf8         bl         imp___stubs__objc_release
0000000101cb7bfc         add        x0, sp, #0x8
0000000101cb7c00         bl         imp___stubs__objc_destroyWeak

那么造成over-release的可能性有2種,一種是objc_release的釋放過程有問題,一種是objc_loadWeakRetained的retain函數(shù)有問題;
關(guān)鍵點出現(xiàn)在objc_loadWeakRetained這個方法上.
其實現(xiàn)如下https://opensource.apple.com/source/objc4/objc4-706/runtime/NSObject.mm.auto.html

id
objc_loadWeakRetained(id *location)
{
    id result;

    SideTable *table;
    
 retry:
    result = *location;
    if (!result) return nil;
    
    table = &SideTables()[result];
    
    table->lock();
    if (*location != result) {
        table->unlock();
        goto retry;
    }

    result = weak_read_no_lock(&table->weak_table, location);

    table->unlock();
    return result;
}

在lock()之前會先給result取值,那么當(dāng)多線程并發(fā)時,此時某個線程里釋放了location,那么result還是一開始指向location,但接下來走lock,再走weak_read_no_lock,就會無效,因為location的指向已經(jīng)被置為nil了,那就相當(dāng)于retain沒有走,然后返回了被釋放的對象的地址;野指針就產(chǎn)生了;
再接下來使用這個地址干任何事情都是可能crash的;

當(dāng)然蘋果自己在objc注釋里也寫了類似如下注釋

This function IS NOT thread-safe with respect to concurrent 
 * modifications to the weak variable. (Concurrent weak clear is safe.)

因此,問題的根本很可能是objc_loadWeakRetained的非多線程安全導(dǎo)致的,再結(jié)合發(fā)現(xiàn)用戶遇到的情況基本都是多線程頻繁回調(diào)該函數(shù)的case,那基本可以斷定就是weak的這個特定的鍋了;

解決辦法也很簡單,不適用weak修飾訪問了唄;多retain一會兒也無妨;

四、結(jié)論

所以:對于容易存在多線程釋放并且存在多線程并發(fā)訪問的對象,不建議使用weak修飾或訪問。畢竟weak的底層實現(xiàn)蘋果也說明了,并不完全是線程安全,盡量減少這種情況即可;
這也可以解釋很多系統(tǒng)庫的莫名其妙的over-release問題。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容