iOS 性能優(yōu)化-weak存儲(chǔ)原理

在日常開(kāi)發(fā)過(guò)程中嗅绸,經(jīng)常會(huì)出現(xiàn)循環(huán)引用而導(dǎo)致的內(nèi)存泄露的問(wèn)題,比如我們有a断国,b兩個(gè)對(duì)象贤姆,對(duì)象中都有兩個(gè)屬性name和age,然后出現(xiàn)了下列情況

a.name = b.name;
b.age = a.age;

  • a對(duì)象通過(guò)a.name引用了b對(duì)象稳衬,所以b的引用計(jì)數(shù)為1
  • b對(duì)象通過(guò)b.age引用了a對(duì)象庐氮,所以a的引用計(jì)數(shù)也為1

這時(shí)a執(zhí)行完任務(wù)后,發(fā)現(xiàn)引用計(jì)數(shù)還是1宋彼,不能釋放弄砍。b執(zhí)行完任務(wù)后,發(fā)現(xiàn)引用計(jì)數(shù)還是1输涕,也不能釋放音婶。
我們看到它們彼此都需要對(duì)方,然后boom莱坎!循環(huán)引用衣式。

這時(shí)候我們可以加一個(gè)__weak修飾,

__weak a.name = b.name;
b.age = a.age;

  • a對(duì)象通過(guò)__weak a.name弱引用了b對(duì)象,所以b的引用計(jì)數(shù)為0碴卧。
  • b對(duì)象通過(guò)b.age引用了a對(duì)象弱卡,所以a的引用計(jì)數(shù)也為1。

這時(shí)住册,a執(zhí)行完任務(wù)后婶博,發(fā)現(xiàn)引用計(jì)數(shù)還是1,不能釋放荧飞。b在執(zhí)行完任務(wù)后引用計(jì)數(shù)變?yōu)?凡人,b對(duì)象被釋放,從而a的引用計(jì)數(shù)減1變成0叹阔,也被釋放挠轴。

那么weak在這里面是如何執(zhí)行的呢?下面開(kāi)始我們的正題耳幢。


weak存儲(chǔ)原理.png

上圖是weak修飾對(duì)象時(shí)系統(tǒng)底層的流程岸晦,下面結(jié)合runtime源碼梳理一下。
1睛藻、objc_initWeak初始化

//location是弱引用對(duì)象的內(nèi)存地址
id objc_initWeak(id *location, id newObj) {
    if (!newObj) {
        *location = nil;
        return nil;
    }

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

2委煤、調(diào)用storeWeak函數(shù)存儲(chǔ)weak弱引用指針
直接看代碼

static id 
storeWeak(id *location, objc_object *newObj)
{
    Class previouslyInitializedClass = nil;
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;
    //如果之前存儲(chǔ)過(guò)這個(gè)弱引用對(duì)象
    if (haveOld) {
        //則把原來(lái)弱引用對(duì)象從weak_table中移除
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }
    //如果這是新的弱引用對(duì)象
    if (haveNew) {
        //把這個(gè)新的弱引用對(duì)象添加到weak_table中
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected
        //判斷這個(gè)對(duì)象是否屬于TaggedPointer類(lèi)型的,如果屬于TaggedPointer類(lèi)型那就厲害了修档,根本不需要有引用計(jì)數(shù)一說(shuō)
        //taggedPointer對(duì)象是為了蘋(píng)果為了性能最大化做的處理碧绞,針對(duì)不需要到棧和堆中尋找的對(duì)象,可以直接從地址中通過(guò)一定的算法得到他們的值吱窝。
        if (newObj  &&  !newObj->isTaggedPointer()) {
            //標(biāo)記該對(duì)象是弱引用對(duì)象
            newObj->setWeaklyReferenced_nolock();
        }

        // Do not set *location anywhere else. That would introduce a race.
        *location = (id)newObj;
    }
    else {
        // No new value. The storage is not changed.
    }
    //給這兩個(gè)表解鎖
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    return (id)newObj;
}

那這里面有兩個(gè)重要的函數(shù)weak_unregister_no_lockweak_register_no_lock
1)首先判斷弱引用指針表中是否有當(dāng)前要存儲(chǔ)的weak弱引用指針讥邻,看代碼

void weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,  id *referrer_id) {

    objc_object *referent = (objc_object *)referent_id;//被弱引用的對(duì)象
    objc_object **referrer = (objc_object **)referrer_id;//弱引用變量的地址

    weak_entry_t *entry;//弱引用指針條目

    if (!referent) return;
    //調(diào)用weak_entry_for_referent找到entry弱引用指針條目
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        //從內(nèi)層inline_referrers中移除entry
        //inline_referrers中只能存儲(chǔ)4個(gè)弱引用指針,多了就要存儲(chǔ)到referrers中院峡,所以要多一步empty判空操作
        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;
                }
            }
        }

        //從weak_table中移除entry弱引用條目
        if (empty) {
            weak_entry_remove(weak_table, entry);
        }
    }

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

2)調(diào)用weak_register_no_lock存儲(chǔ)entry弱引用指針條目兴使,看代碼

id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, bool crashIfDeallocating)
{
    objc_object *referent = (objc_object *)referent_id;//被弱引用的對(duì)象
    objc_object **referrer = (objc_object **)referrer_id;//弱引用變量的地址

    //如果該弱引用對(duì)象是taggedPointer對(duì)象,則不做處理直接返回該對(duì)象
    //taggedPointer對(duì)象是為了蘋(píng)果為了性能最大化做的處理照激,針對(duì)不需要到棧和堆中尋找的對(duì)象发魄,可以直接從地址中通過(guò)一定的算法得到他們的值。
    if (!referent  ||  referent->isTaggedPointer()) return referent_id;

    // ensure that the referenced object is viable
    bool deallocating;
    if (!referent->ISA()->hasCustomRR()) {
        deallocating = referent->rootIsDeallocating();
    }
    else {
        BOOL (*allowsWeakReference)(objc_object *, SEL) = 
            (BOOL(*)(objc_object *, SEL))
            object_getMethodImplementation((id)referent, 
                                           SEL_allowsWeakReference);
        if ((IMP)allowsWeakReference == _objc_msgForward) {
            return nil;
        }
        deallocating =
            ! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
    }

    if (deallocating) {
        if (crashIfDeallocating) {
            _objc_fatal("Cannot form weak reference to instance (%p) of "
                        "class %s. It is possible that this object was "
                        "over-released, or is in the process of deallocation.",
                        (void*)referent, object_getClassName((id)referent));
        } else {
            return nil;
        }
    }

    // now remember it and where it is being stored
    weak_entry_t *entry;//弱引用指針的條目
    //判斷weak_table中是否有該條目
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        //如果有俩垃,則把弱引用對(duì)象追加進(jìn)去
        append_referrer(entry, referrer);
    } 
    else {
        //如果沒(méi)有励幼,則創(chuàng)建一個(gè)
        weak_entry_t new_entry(referent, referrer);
        //如果索引已經(jīng)超過(guò)原來(lái)的3/4,則給weak_table擴(kuò)容
        weak_grow_maybe(weak_table);
        //將新的entry插入weak_table
        weak_entry_insert(weak_table, &new_entry);
    }

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

    return referent_id;
}

綜上口柳,就是系統(tǒng)在使用weak的存儲(chǔ)原理苹粟,當(dāng)然也可以繼續(xù)深入的探索,日后會(huì)抽時(shí)間繼續(xù)探索噠跃闹,希望能和大家共同進(jìn)步嵌削。
參考博客:
iOS管理對(duì)象內(nèi)存的數(shù)據(jù)結(jié)構(gòu)以及操作算法--SideTables毛好、RefcountMap、weak_table_t-一
Objective-C 小記(10)__weak
深入淺出ARC(上)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末苛秕,一起剝皮案震驚了整個(gè)濱河市肌访,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌艇劫,老刑警劉巖吼驶,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異港准,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)咧欣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)浅缸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人魄咕,你說(shuō)我怎么就攤上這事衩椒。” “怎么了哮兰?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵毛萌,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我喝滞,道長(zhǎng)阁将,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任右遭,我火速辦了婚禮做盅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘窘哈。我一直安慰自己吹榴,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布滚婉。 她就那樣靜靜地躺著图筹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪让腹。 梳的紋絲不亂的頭發(fā)上远剩,一...
    開(kāi)封第一講書(shū)人閱讀 49,749評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音骇窍,去河邊找鬼民宿。 笑死,一個(gè)胖子當(dāng)著我的面吹牛像鸡,可吹牛的內(nèi)容都是我干的活鹰。 我是一名探鬼主播哈恰,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼志群!你這毒婦竟也來(lái)了着绷?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤锌云,失蹤者是張志新(化名)和其女友劉穎荠医,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體桑涎,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡彬向,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了攻冷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片娃胆。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖等曼,靈堂內(nèi)的尸體忽然破棺而出里烦,到底是詐尸還是另有隱情,我是刑警寧澤禁谦,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布胁黑,位于F島的核電站,受9級(jí)特大地震影響州泊,放射性物質(zhì)發(fā)生泄漏丧蘸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一遥皂、第九天 我趴在偏房一處隱蔽的房頂上張望触趴。 院中可真熱鬧,春花似錦渴肉、人聲如沸冗懦。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)披蕉。三九已至,卻和暖如春乌奇,著一層夾襖步出監(jiān)牢的瞬間没讲,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工礁苗, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留爬凑,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓试伙,卻偏偏與公主長(zhǎng)得像嘁信,于是被迫代替她去往敵國(guó)和親于样。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348