iOS Objective-C底層 part3:live^reference

本文閱讀的objc源碼

一提到ARC內(nèi)的引用(reference),條件反射式想起的就是weak+retainCount+retain+release等一堆詞.

retain+release是對(duì)retainCount的操作不必多說(shuō).
weak的功能:通過(guò)weak指針能獲取到某個(gè)對(duì)象,卻不增加對(duì)象的retainCount,對(duì)象銷毀,weak指針自動(dòng)置為nil.

那么這些weak+retainCount是靠怎么實(shí)現(xiàn)的呢?
答案:
weak 靠 對(duì)象優(yōu)化的isaSideTables結(jié)實(shí)使用
retainCount 靠 對(duì)象優(yōu)化的isaSideTables結(jié)實(shí)使用

優(yōu)化的isa在前面已經(jīng)提過(guò),里面的9個(gè)字段各有用處,忘了請(qǐng)戳.

1. SideTables

SideTables.png

SideTables是一個(gè)全局的Hash表,以對(duì)象的地址為Key能拿到對(duì)應(yīng)的SideTable.SideTable就是管理retainCountweak指針指向的最小單位.

SideTable:

spinlock_t slock;//操作retainCount和weak指針指向的操作比較頻繁,slock是保證操作的線程安全的
RefcountMap refcnts;//管理retainCount
weak_table_t weak_table;//管理weak指針指向
1.1 RefcountMap
  • 二層過(guò)濾

很奇怪吧!保存一個(gè)對(duì)象的retainCount隨便弄塊內(nèi)存空間不就完事了嗎?還用一個(gè)Map干嘛?我們又得回到開始:SideTables是一個(gè)全局的Hash表,是Hash表就會(huì)有沖突(關(guān)于Hash表有不明白的請(qǐng)戳).當(dāng)不同地址的兩個(gè)對(duì)象對(duì)應(yīng)同一個(gè)SideTable時(shí),這時(shí)就需要RefcountMap應(yīng)用對(duì)象地址來(lái)做第二層過(guò)濾.所以你看到RefcountMap refcnts到存儲(chǔ)retainCount的內(nèi)存之間要用一個(gè)find()方法.

  • 存儲(chǔ)

在找到真正存儲(chǔ)對(duì)象retainCount的64位空間后還要注意:

#define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0)
#define SIDE_TABLE_DEALLOCATING      (1UL<<1)  

64位的倒數(shù)第一位標(biāo)記當(dāng)前對(duì)象是否被weak指針指向(1:有weak指針指向);
64位的倒數(shù)第二位標(biāo)記當(dāng)前對(duì)象是否正在銷毀狀態(tài)(1:處在正在銷毀狀態(tài));
其他的62位都可以用于存儲(chǔ)retainCount.

1.2 weak_table_t
  • 二層過(guò)濾

也是因?yàn)?code>Hash表沖突,weak_table_t有一個(gè)weak_entry_t數(shù)組,也就是說(shuō)在進(jìn)入weak_table_t也需要通過(guò)遍歷才能確定真正管理某個(gè)對(duì)象weak指針指向weak_entry_t.

struct weak_entry_t {
    DisguisedPtr<objc_object> referent;
    union {
        struct {
            weak_referrer_t *referrers;
            uintptr_t        out_of_line_ness : 2;
            uintptr_t        num_refs : PTR_MINUS_2;
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        struct {
            // out_of_line_ness field is low bits of inline_referrers[1]
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };
};

referent==>對(duì)象地址,用于weak_entry_t 數(shù)組遍歷時(shí)的比對(duì);

聯(lián)合體內(nèi)struct1->weak_referrer_t *referrers;
聯(lián)合體內(nèi)struct2->inline_referrers[WEAK_INLINE_COUNT]
weak變量的指針個(gè)數(shù)不超過(guò)4個(gè)用inline_referrers,
weak變量的指針個(gè)數(shù)超過(guò)4個(gè)用referrers.

到此為止,SideTables的結(jié)構(gòu)已經(jīng)介紹完畢,weakretainCount的實(shí)現(xiàn)會(huì)在后面再做具體說(shuō)明(retainCount的實(shí)現(xiàn)需要SideTables以外的東西,難度有點(diǎn)大,先給您提個(gè)醒).

2. retainCount的實(shí)現(xiàn)

上面已經(jīng)說(shuō)過(guò)Sidetables->Sidetable sidetable->RefcountMap refcnts來(lái)保存對(duì)象的retainCount.但是在對(duì)象的最開始階段不用以上方案,只有retainCount大到一定程度才會(huì)用以上方案.具體如下:

首先,讓我們回到類對(duì)象的isa上( arm64版本)

union isa_t 
{
    Class cls;
    uintptr_t bits;
struct {
        uintptr_t nonpointer : 1;
        uintptr_t has_assoc : 1;
        uintptr_t has_cxx_dtor : 1;
        uintptr_t shiftcls : 33;
        uintptr_t magic : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating : 1;
        uintptr_t has_sidetable_rc : 1;
        uintptr_t extra_rc : 19;
    };
}

目標(biāo)鎖定:has_sidetable_rc(1位)+extra_rc(19位).這就是一個(gè)對(duì)象存儲(chǔ)retainCount的兩個(gè)字段.
剛開始isa.has_sidetable_rc則等于0,retainCount會(huì)存在isa.extra_rc上,isa.extra_rc有19位,可以存儲(chǔ)2^20-1= 1048575.
當(dāng)retainCount大到超出isa.extra_rc的顯示范圍,會(huì)借助Sidetable來(lái)繼續(xù)記錄retainCount,而一旦啟用Sidetable來(lái)繼續(xù)記錄retainCount,isa.has_sidetable_rc則等于1.

2.1 retainCount在哪里

很確切的說(shuō)對(duì)象的isa內(nèi)的has_sidetable_rc+extra_rc只保存"多余"的retainCount,

- (NSUInteger)retainCount {
    return ((id)self)->rootRetainCount();
}

inline uintptr_t objc_object::rootRetainCount() {
    isa_t bits = LoadExclusive(&isa.bits);
    uintptr_t rc = 1 + bits.extra_rc;
    if (bits.has_sidetable_rc) {
        rc += sidetable_getExtraRC_nolock();
    }
    return rc;
}

由上可以得到retainCount的組成:

1
extra_rc //中存儲(chǔ)的值
sidetable_getExtraRC_nolock //Sidetable存儲(chǔ)的值

這樣只保存"多余"的retainCount,可以免去很多不必要的操作(如:對(duì)象初始化完成后就不必對(duì)retainCount操作,在對(duì)象存儲(chǔ)著的retainCount等于0的情況下,再release一次直接調(diào)用dealloc也不必再操作retainCount).

2.2 加retainCount retain

retain的調(diào)用棧:


retain.png
ALWAYS_INLINE id 
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
    if (isTaggedPointer()) return (id)this;

    bool sideTableLocked = false;
    bool transcribeToSideTable = false;

    isa_t oldisa;
    isa_t newisa;

    do {
        transcribeToSideTable = false;
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            else return sidetable_retain();
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        if (slowpath(tryRetain && newisa.deallocating)) {
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            return nil;
        }
        uintptr_t carry;
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++

        if (slowpath(carry)) {
            // newisa.extra_rc++ overflowed
            if (!handleOverflow) {
                ClearExclusive(&isa.bits);
                return rootRetain_overflow(tryRetain);
            }
            // Leave half of the retain counts inline and 
            // prepare to copy the other half to the side table.
            if (!tryRetain && !sideTableLocked) sidetable_lock();
            sideTableLocked = true;
            transcribeToSideTable = true;
            newisa.extra_rc = RC_HALF;
            newisa.has_sidetable_rc = true;
        }
    } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));

    if (slowpath(transcribeToSideTable)) {
        // Copy the other half of the retain counts to the side table.
        sidetable_addExtraRC_nolock(RC_HALF);
    }

    if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
    return (id)this;
}

  • 只加isa.extra_rc

這個(gè)操作相對(duì)簡(jiǎn)單:
1.oldisa = LoadExclusive(&isa.bits);==>讀取isa;
2.newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);//extra_rc++==>isa.extra_rc加1;
3.StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)==>更新isa.

  • 超出isa.extra_rc的存儲(chǔ)范圍,啟用Sidetable

經(jīng)過(guò)第1步與第2步發(fā)現(xiàn)溢出,遞歸調(diào)用rootRetain(bool tryRetain, bool handleOverflow)并將handleOverflow標(biāo)記為ture;
重復(fù)第1步與第2步,溢出,設(shè)置isa.extra_rc = RC_HALF;調(diào)用StoreExclusive更新isa;

RC_HALF
define RC_HALF (1ULL<<18)
isa.extra_rc占19位,超出范圍就是 (1ULL<<19), (1ULL<<19)/2 = (1ULL<<18)
// Copy the other half of the retain counts to the side table.正如注釋所說(shuō)的拿出一半的retaincount讓Sidetable存儲(chǔ),另一半還有由isa.extra_rc存儲(chǔ)

操作Sidetable:

bool 
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
    assert(isa.nonpointer);
    SideTable& table = SideTables()[this];

    size_t& refcntStorage = table.refcnts[this];
    size_t oldRefcnt = refcntStorage;
    // isa-side bits should not be set here
    assert((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
    assert((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);

    if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;

    uintptr_t carry;
    size_t newRefcnt = 
        addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
    if (carry) {
        refcntStorage =
            SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
        return true;
    }
    else {
        refcntStorage = newRefcnt;
        return false;
    }
}

SideTable& table = SideTables()[this];==>SideTables以對(duì)象地址為Key獲取對(duì)應(yīng)的SideTable;
size_t& refcntStorage = table.refcnts[this];==>SideTable table->RefcountMap refcnts內(nèi)用對(duì)象的地址獲取對(duì)應(yīng)的對(duì)象的retainCount(即:refcntStorage);
最后兩位是有意義的標(biāo)志位(前面說(shuō)SideTables已經(jīng)交代過(guò)了),所以加的時(shí)候要左移兩位(SIDE_TABLE_RC_SHIFT),將加好的newRefcnt存回refcntStorage.

當(dāng)然操作Sidetable也有判斷溢出,不過(guò)返回真假出去已經(jīng)沒(méi)人管了.

2.3 減retainCount release
release.png
  • 只減isa.extra_rc

1.oldisa = LoadExclusive(&isa.bits);==>讀取isa;
2.newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);//extra_rc--==>isa.extra_rc減1;
3.StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)==>更新isa.

  • 由Sidetable中借位,再減

isa.extra_rc剪到溢出,跳轉(zhuǎn)到underflow,看isa.has_sidetable_rc是否為1,為0直接去做dealloc方面的工作(dealloc的具體實(shí)現(xiàn)后面也會(huì)細(xì)說(shuō)).為1則遞歸調(diào)用rootRelease(bool performDealloc, bool handleUnderflow)并將handleUnderflow標(biāo)記為ture;
重復(fù)以上流程后,向Sidetable借位,借出RC_HALF,然后將借出的-1賦給isa.extra_rc,保存.

3. weak的實(shí)現(xiàn)

對(duì)于weak的實(shí)現(xiàn),我們先來(lái)個(gè)卡通版的理解:
全局維護(hù)了一個(gè)weak表,用于存儲(chǔ)指向?qū)ο蟮乃?code>weak指針.存儲(chǔ)方式:Key是對(duì)象的地址,Valueweak變量的指針的數(shù)組

NSObject * object = [[NSObject alloc]init];
__weak NSObject * weak_object1 = object;
__weak NSObject * weak_object2 = object;
    
NSLog(@"strong-point-to-%@",object);
NSLog(@"weak1-point-to-%@",weak_object1);
NSLog(@"weak2-point-to-%@",weak_object2);
    
NSLog(@"strong-point-address-%p",&object);
NSLog(@"weak1-point-address-%p",&weak_object1);
NSLog(@"weak2-point-address-%p",&weak_object2);
打印:
strong-point-to-<NSObject: 0x61800000a9a0>
weak1-point-to-<NSObject: 0x61800000a9a0>
weak2-point-to-<NSObject: 0x61800000a9a0>
strong-point-address-0x7fff56c7d9e8
weak1-point-address-0x7fff56c7d9e0
weak2-point-address-0x7fff56c7d9d8
Key Value
0x61800000a9a0 @[0x7fff56c7d9e8,0x7fff56c7d9e0,0x7fff56c7d9d8]

1.這樣的綁定,當(dāng)然可以讓weak變量的指針順利的拿到對(duì)應(yīng)的對(duì)象,而且沒(méi)有增加對(duì)象的引用計(jì)數(shù).
2.當(dāng)作為Key的對(duì)象地址上的對(duì)象銷毀的時(shí)候也會(huì)主動(dòng)將作為Valueweak變量的指針的數(shù)組內(nèi)的每個(gè)地址都指向nil.

對(duì)象的isa.weakly_referenced的作用就是對(duì)對(duì)象自身有沒(méi)有被weak變量的指針指向的標(biāo)記:
當(dāng)對(duì)象被weak變量的指針指向的時(shí)候isa.weakly_referenced=1,否則isa.weakly_referenced=0.
這樣的標(biāo)記是為了在對(duì)象銷毀時(shí)判斷要不要進(jìn)行獲取對(duì)象對(duì)應(yīng)的weak變量的指針的數(shù)組并做置為nil的操作.具體實(shí)現(xiàn)dealloc篇章內(nèi)會(huì)說(shuō).

當(dāng)然以上是個(gè)卡通版的理解.具體集合Sidetables就是:
1.依據(jù)對(duì)象地址,在Sidetables拿到對(duì)應(yīng)的Sidetable

2.而在Sidetable sidetable->weak_table_t weak_table->weak_entry_t *weak_entries通過(guò)遍歷比對(duì)找到對(duì)應(yīng)的weak_entry_t.

3.在weak_entry_t內(nèi)的weak_referrer_t *referrers或者weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
前面已經(jīng)講過(guò):
weak變量的指針個(gè)數(shù)不超過(guò)4個(gè)用inline_referrers,
weak變量的指針個(gè)數(shù)超過(guò)4個(gè)用referrers.

文章參考:
objc源碼
iOS管理對(duì)象內(nèi)存的數(shù)據(jù)結(jié)構(gòu)以及操作算法--SideTables、RefcountMap、weak_table_t

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

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

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