iOS底層-- weak修飾對象存儲原理

問題:為何weak修飾的變量可以打破循環(huán)引用?
因為weak修飾的變量存儲在散列表中的弱引用表里,不參與引用計數(shù)器的使用帮匾,也就是說瞻离,在進(jìn)行釋放額時候,不管你怎么引用献汗,直接就把你置空了。

基本概念

  • SideTable :散列表:系統(tǒng)自動維護(hù),用于存儲/管理一些信息
    SideTable的結(jié)構(gòu)中能看到 其管理三種表:
    spinlock_t slock 自旋鎖表;
    RefcountMap refcnts 引用計數(shù)表;
    weak_table_t weak_table 弱引用表;
  • weak_table:弱引用對象存儲的表摹迷,是SideTable中的一張表
  • weak_entry_t:weak_table里面的一個單元,用于管理當(dāng)前類的弱引用對象郊供,可以理解為一個數(shù)組峡碉,查看weak_entry_t的結(jié)構(gòu),有助于更好的理解里面的存儲結(jié)構(gòu)驮审,里面包含一個weak_referrer_t鲫寄,相當(dāng)于一個數(shù)組,這里面的就是存儲的弱引用對象疯淫,還有其他的一些信息塔拳,比如mask(蒙版,容量-1)峡竣、num_refs (當(dāng)前存儲的數(shù)量)等
  • weak_referrer_t :weak_entry_t 中的弱引用對象靠抑,相當(dāng)于是 數(shù)組中的一個元素

存儲原理

1、源碼探索入口

寫上這樣的代碼适掰,打上斷點颂碧,并打開匯編模式:debug->debug workflow -> alway show disassembly

- (instancetype)init
{
    if (self = [super init]) {        
?        id __weak weakSelf = self;    //斷點在這
    }
    return self;
}

運行后會進(jìn)入斷點荠列,出現(xiàn)這樣的一些信息


匯編模式信息

找到callq方法:objc_initWeak ,拿到這個方法就可以進(jìn)入源碼去調(diào)試了

源碼探索

id objc_initWeak(id *location, id newObj)
{
   .....
    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>  (location, (objc_object*)newObj);
}

1.1、內(nèi)部做的操作是 存儲weak—storeWeak

static id 
storeWeak(id *location, objc_object *newObj)
{
    ......

    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);

    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        goto retry;
    }

//有新值载城,判斷類有沒有初始化肌似,如果沒有初始化,就進(jìn)行初始化
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            class_initialize(cls, (id)newObj);
            goto retry;
        }
    }

// 有舊值诉瓦,進(jìn)行 weak_unregister_no_lock操作
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

// 有新值  進(jìn)行weak_register_no_lock 操作
    if (haveNew) {
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating);

        if (newObj  &&  !newObj->isTaggedPointer()) {
            newObj->setWeaklyReferenced_nolock();
        }
        *location = (id)newObj;
    }
    else {
    }
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    return (id)newObj;
}

關(guān)鍵步驟:
1川队、如果cls還沒有 初始化,先初始化這個類

2睬澡、如果weak對象有舊值固额,先對舊值 進(jìn)行 weak_unregister_no_lock,刪除舊值
3煞聪、如果weak對象有新值 就對新值進(jìn)行weak_register_no_lock斗躏,新增新值

1.2、再來看weak_unregister_no_lock,刪除舊值

void weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, 
                        id *referrer_id)
{
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;

    weak_entry_t *entry;

    if (!referent) return;
// 在 weak_table 中去找到 有 referent 的 entry (相當(dāng)于在weak_table 表中去找到包含referent 元素的 數(shù)組)
   if ((entry = weak_entry_for_referent(weak_table, referent))) {

// 找到了 這個  entry昔脯,  就刪除 entry 中的  引用對象-referrer
        remove_referrer(entry, referrer);
        bool empty = true;
        if (entry->out_of_line()  &&  entry->num_refs != 0) {
            empty = false;
        }
        else {
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                if (entry->inline_referrers[i]) {
                    empty = false; 
                    break;
                }
            }
        }

// 如果 entry 中的引用對象 沒有了 刪除這個 entry
        if (empty) {
            weak_entry_remove(weak_table, entry);
        }
    }
}

關(guān)鍵步驟
1啄糙、在 weak_table 中去找到 有 referent-引用對象的 entry (相當(dāng)于在weak_table 表中去找到包含referent 元素的 數(shù)組)
2、如果找到了 entry 就刪除 entry中的 referent-引用對象
3云稚、判斷 entry 里面 還有沒有其他對象隧饼,如果沒有,就把entry也remove掉(相當(dāng)于數(shù)組中的元素為空静陈,就把這個數(shù)據(jù)也刪掉)

1.3桑李、 存儲新值:weak_register_no_lock

id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, bool crashIfDeallocating)
{
    ......

    weak_entry_t *entry;
// 在 weak_table 中去找到 有 referent 的 entry (相當(dāng)于在weak_table 表中去找到包含referent 元素的 數(shù)組)
    if ((entry = weak_entry_for_referent(weak_table, referent))) {

// 如果找到,直接append
        append_referrer(entry, referrer);
    } 
    else {
// 如果沒有找到相應(yīng)的 entry 窿给,就創(chuàng)建一個entry 并插入 weak_table
        weak_entry_t new_entry(referent, referrer);
        weak_grow_maybe(weak_table);
        weak_entry_insert(weak_table, &new_entry);
    }

    // Do not set *referrer. objc_storeWeak() requires that the 
    // value not change.

    return referent_id;
}

關(guān)鍵步驟
1贵白、在 weak_table 中去找 有 referent 的 entry (相當(dāng)于在weak_table 表中去找到包含referent 元素的 數(shù)組)
2、如果找到entry崩泡,進(jìn)行添加操作:append_referrer
- 2.1禁荒、如果有空位,直接插進(jìn)去
---- 這里有一個疑問:為什么會有一個空位角撞?這里可以看new_entry的實現(xiàn):初始容量為4呛伴,并默認(rèn)4個空值
- 2.2、如果數(shù)量超過容量的3/4谒所,進(jìn)行擴容热康,再添加(這里想到,方法緩存機制劣领,方法緩存也是超過3/4進(jìn)行擴容姐军,方法的擴容是:擴容之后,以前的方法刪掉了,再把需要緩存的方法插進(jìn)去)
3奕锌、如果沒找到entry著觉,創(chuàng)建一個entry,在進(jìn)行插入


大概的過程是這樣的:


大概流程

釋放原理

弱引用對象在釋放的時候惊暴,可以在dealloc中去看具體是怎么釋放的
dealloc -》
rootDealloc -》
object_dispose -》
objc_destructInstance -》
clearDeallocating -》
clearDeallocating_slow

void *objc_destructInstance(id obj) 
{
    if (obj) {
        bool assoc = obj->hasAssociatedObjects();
// 如果有關(guān)聯(lián)對象饼丘,就remove掉 
        if (assoc) _object_remove_assocations(obj);

// 弱引用的釋放在這里
        obj->clearDeallocating();
    }

    return obj;
}

inline void 
objc_object::clearDeallocating()
{
    if (slowpath(!isa.nonpointer)) {
        // Slow path for raw pointer isa.
        sidetable_clearDeallocating();
    }
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
  //  在這里進(jìn)行釋放
        clearDeallocating_slow();
    }

    assert(!sidetable_present());
}


// 找到散列表中的 weak_table 表,找到weak_table 中的 entry辽话,將entry中的 引用對象referrer 置空肄鸽,最后remove entry
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
    ASSERT(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));

    SideTable& table = SideTables()[this];
    table.lock();
    if (isa.weakly_referenced) {
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
    table.unlock();
}

總之,釋放的時候就是找到散列表中的 weak_table 表油啤,找到weak_table 中的 entry典徘,將entry中的 引用對象referrer 置空,最后remove entry

最后補充一張存儲結(jié)構(gòu)圖 來源

存儲結(jié)構(gòu)圖

補充

  • 1村砂、strong
    同樣的方法,進(jìn)入strong底層屹逛,可以看到础废,底層默認(rèn)實現(xiàn)的是 retain 方法,所以在arc中罕模,strong其實等同于 retrain

  • 2评腺、weak與assign
    assign一般只修飾值類型,雖然也可以修飾引用類型淑掌,但是修飾的對象釋放后蒿讥,指針不會自動被置空,此時向?qū)ο蟀l(fā)消息會崩潰抛腕。
    weak 不會產(chǎn)生野指針問題芋绸。因為weak修飾的對象釋放后(引用計數(shù)器值為0),指針會自動被置nil担敌,之后再向該對象發(fā)消息也不會崩潰摔敛。 weak是安全的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末全封,一起剝皮案震驚了整個濱河市马昙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌刹悴,老刑警劉巖行楞,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異土匀,居然都是意外死亡子房,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來池颈,“玉大人尾序,你說我怎么就攤上這事∏椋” “怎么了每币?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長琢歇。 經(jīng)常有香客問我兰怠,道長,這世上最難降的妖魔是什么李茫? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任揭保,我火速辦了婚禮,結(jié)果婚禮上魄宏,老公的妹妹穿的比我還像新娘秸侣。我一直安慰自己,他們只是感情好宠互,可當(dāng)我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布味榛。 她就那樣靜靜地躺著,像睡著了一般予跌。 火紅的嫁衣襯著肌膚如雪搏色。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天券册,我揣著相機與錄音频轿,去河邊找鬼。 笑死烁焙,一個胖子當(dāng)著我的面吹牛航邢,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播骄蝇,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼翠忠,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了乞榨?” 一聲冷哼從身側(cè)響起秽之,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吃既,沒想到半個月后考榨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡鹦倚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年河质,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡掀鹅,死狀恐怖散休,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情乐尊,我是刑警寧澤戚丸,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站扔嵌,受9級特大地震影響限府,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜痢缎,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一胁勺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧独旷,春花似錦署穗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至咱台,卻和暖如春络拌,著一層夾襖步出監(jiān)牢的瞬間俭驮,已是汗流浹背回溺。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留混萝,地道東北人遗遵。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像逸嘀,于是被迫代替她去往敵國和親车要。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,435評論 2 359