RunTime源碼閱讀(一)之weak

讀weak之前先了解三個(gè)數(shù)據(jù)結(jié)構(gòu):SideTable盐肃、weak_table_t腥沽、weak_entry_t

一申眼、基本數(shù)據(jù)結(jié)構(gòu)

1.SideTable結(jié)構(gòu)體

管理著引用計(jì)數(shù)表與弱引用表

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;//引用計(jì)數(shù)表
    weak_table_t weak_table;//弱引用表
    ...
};

2.weak_table_t結(jié)構(gòu)體

全局弱引用表划煮。將對(duì)象id存儲(chǔ)為鍵,和weak_entry_t結(jié)構(gòu)作為它們的值。

struct weak_table_t {
    weak_entry_t *weak_entries;// hash數(shù)組磕秤,用來(lái)存儲(chǔ)弱引用對(duì)象的相關(guān)信息weak_entry_t
    size_t    num_entries;// hash數(shù)組中的元素個(gè)數(shù)
    uintptr_t mask;// hash數(shù)組長(zhǎng)度-1筑累,會(huì)參與hash計(jì)算。(注意逻炊,這里是hash數(shù)組的長(zhǎng)度互亮,而不是元素個(gè)數(shù)。比如余素,數(shù)組長(zhǎng)度可能是64豹休,而元素個(gè)數(shù)僅存了2個(gè))
    uintptr_t max_hash_displacement;// 可能會(huì)發(fā)生的hash沖突的最大次數(shù)
};

3. weak_entry_t

靜態(tài)數(shù)組與動(dòng)態(tài)數(shù)組結(jié)合的結(jié)構(gòu)體,數(shù)量小于等于4存靜態(tài)數(shù)組中桨吊,大于4存動(dòng)態(tài)數(shù)組

struct weak_entry_t {
    DisguisedPtr<objc_object> referent;// 被弱引用的對(duì)象
    // 引用該對(duì)象的對(duì)象列表威根,聯(lián)合。 引用個(gè)數(shù)小于4视乐,用inline_referrers數(shù)組洛搀。 用個(gè)數(shù)大于4,用動(dòng)態(tài)數(shù)組weak_referrer_t *referrers
    union {
        struct {
            weak_referrer_t *referrers;// 弱引用該對(duì)象的對(duì)象列表的動(dòng)態(tài)數(shù)組
            uintptr_t        out_of_line_ness : 2;// 是否使用動(dòng)態(tài)數(shù)組標(biāo)記位
            uintptr_t        num_refs : PTR_MINUS_2;// 動(dòng)態(tài)數(shù)組中有效元素個(gè)數(shù)
            uintptr_t        mask;//動(dòng)態(tài)數(shù)組元素個(gè)數(shù)
            uintptr_t        max_hash_displacement;// 最大的hash沖突次數(shù)(說(shuō)明了最多做max_hash_displacement次hash沖突佑淀,肯定會(huì)找到對(duì)應(yīng)的數(shù)據(jù))
        };
        struct {
            // out_of_line_ness field is low bits of inline_referrers[1]
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };
    ...
};

二留美、初始化

1.入口

//weak源碼分析
void weak(){
    Person * person = [[Person alloc] init];

    //指針地址指向指針
    __weak Person *weakPerson = person;
    __weak Person *weakPerson2 = person;
    NSLog(@"person指針:%p\nweakPerson指針:%p",person,weakPerson);
    NSLog(@"person指針地址:%p\nweakPerson指針地址:%p",&person,&weakPerson);
}

流程:objc_initWeak(id *location, id newObj)->storeWeak(id *location, objc_object *newObj)->weak_register_no_lock(...)

2.weak_register_no_lock

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;//指針地址

    // 如果referent為nil 或 referent 采用了TaggedPointer計(jì)數(shù)方式,直接返回渣聚,不做任何操作
    if (!referent  ||  referent->isTaggedPointer()) return referent_id;

    // ensure that the referenced object is viable
    // 確保被引用的對(duì)象可用(沒(méi)有在析構(gòu)独榴,同時(shí)應(yīng)該支持weak引用)
    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);
    }
    // 正在析構(gòu)的對(duì)象,不能夠被弱引用
    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_table中找到referent對(duì)應(yīng)的weak_entry,并將referrer加入到weak_entry中
    weak_entry_t *entry;
    if ((entry = weak_entry_for_referent(weak_table, referent))) {// 如果能找到weak_entry,則講referrer插入到weak_entry中
        append_referrer(entry, referrer);// 將referrer追加到weak_entry_t的引用數(shù)組中
    } 
    else {
        weak_entry_t new_entry(referent, referrer);//創(chuàng)建weak_entry_t
        weak_grow_maybe(weak_table);//擴(kuò)大數(shù)組操作
        weak_entry_insert(weak_table, &new_entry);//new_entry插入weak_table
    }
    return referent_id;
}
  1. 對(duì)象指針不能為空奕枝,非TaggedPointer對(duì)象棺榔。
  2. 判斷對(duì)象沒(méi)有析構(gòu)
  3. 最主要是最后面一段:
    1.分配一個(gè)新的new_entry對(duì)象
    2.weak_grow_maybe判斷weak_table是否需要擴(kuò)容
    3.new_entry插入到weak_table中
    4.如果weak_table中有referent,用append_referrer追加到entry中隘道。

3.weak_entry_for_referent(weak_table, referent)

查找weak_table是否包含了referent症歇。一段while循環(huán)查找。

static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
    assert(referent);

    weak_entry_t *weak_entries = weak_table->weak_entries;

    if (!weak_entries) return nil;

    size_t begin = hash_pointer(referent) & weak_table->mask;
    size_t index = begin;
    size_t hash_displacement = 0;
    while (weak_table->weak_entries[index].referent != referent) {
        index = (index+1) & weak_table->mask;
        if (index == begin) bad_weak_table(weak_table->weak_entries);
        hash_displacement++;
        if (hash_displacement > weak_table->max_hash_displacement) {
            return nil;
        }
    }
    
    return &weak_table->weak_entries[index];
}

4.weak_grow_maybe

判斷weak_table是否需要擴(kuò)容操作谭梗,當(dāng)實(shí)際個(gè)數(shù)>=總長(zhǎng)度的3/4時(shí)忘晤,在原有基礎(chǔ)上擴(kuò)大2倍。

static void weak_grow_maybe(weak_table_t *weak_table)
{
    size_t old_size = TABLE_SIZE(weak_table);

    // Grow if at least 3/4 full.
    if (weak_table->num_entries >= old_size * 3 / 4) {// 當(dāng)大于現(xiàn)有長(zhǎng)度的3/4時(shí)激捏,會(huì)做數(shù)組擴(kuò)容操作设塔。
        weak_resize(weak_table, old_size ? old_size*2 : 64);// 初次會(huì)分配64個(gè)位置,之后在原有基礎(chǔ)上*2
    }
}

5.weak_entry_insert把new_entry插入到weak_table_t中远舅。

三.銷(xiāo)毀

流程:objc_destroyWeak->storeWeak->weak_unregister_no_lock->remove_referrer

1.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;
    // 如果referent為nil 或 referent 采用了TaggedPointer計(jì)數(shù)方式闰蛔,直接返回痕钢,不做任何操作
    if (!referent) return;

    if ((entry = weak_entry_for_referent(weak_table, referent))) {// 查找
        remove_referrer(entry, referrer);// 在referent所對(duì)應(yīng)的weak_entry_t的hash數(shù)組中,移除referrer
        
        // 移除元素之后序六, 要檢查一下weak_entry_t的hash數(shù)組是否已經(jīng)空了
        bool empty = true;
        //使用了可變數(shù)組任连,且數(shù)量不為0
        if (entry->out_of_line()  &&  entry->num_refs != 0) {
            empty = false;
        }
        else {
            //靜態(tài)數(shù)組有值
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                if (entry->inline_referrers[i]) {
                    empty = false; 
                    break;
                }
            }
        }

        if (empty) {// 如果weak_entry_t的hash數(shù)組已經(jīng)空了,則需要將weak_entry_t從weak_table中移除
            weak_entry_remove(weak_table, entry);
        }
    }

}
  1. weak_entry_for_referent在weak_table查找referent例诀,entry不為空做移除
  2. remove_referrer從weak_table移除referrer
  3. out_of_line動(dòng)態(tài)數(shù)組標(biāo)識(shí)與靜態(tài)數(shù)組如果還不為空随抠,weak_entry_remove移除entry

2.remove_referrer

static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
    if (! entry->out_of_line()) {//靜態(tài)數(shù)組
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            if (entry->inline_referrers[i] == old_referrer) {
                entry->inline_referrers[i] = nil;
                return;
            }
        }
        _objc_inform("Attempted to unregister unknown __weak variable "
                     "at %p. This is probably incorrect use of "
                     "objc_storeWeak() and objc_loadWeak(). "
                     "Break on objc_weak_error to debug.\n", 
                     old_referrer);
        objc_weak_error();
        return;
    }
    //動(dòng)態(tài)數(shù)組
    size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    while (entry->referrers[index] != old_referrer) {
        index = (index+1) & entry->mask;
        if (index == begin) bad_weak_table(entry);
        hash_displacement++;
        if (hash_displacement > entry->max_hash_displacement) {
            _objc_inform("Attempted to unregister unknown __weak variable "
                         "at %p. This is probably incorrect use of "
                         "objc_storeWeak() and objc_loadWeak(). "
                         "Break on objc_weak_error to debug.\n", 
                         old_referrer);
            objc_weak_error();
            return;
        }
    }
    entry->referrers[index] = nil;
    entry->num_refs--;
}

如果是靜態(tài)數(shù)組,置空后直接return.否則動(dòng)態(tài)數(shù)組釋放

3.weak_entry_remove

static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
{
    // remove entry
    if (entry->out_of_line()) free(entry->referrers);
    bzero(entry, sizeof(*entry));//清理entry sizeof(*entry)個(gè)字節(jié)

    weak_table->num_entries--;
    weak_compact_maybe(weak_table);//收容
}

釋放繁涂,收容拱她。

4.釋放

局部變量在方法結(jié)束后立即釋放,全局變量會(huì)在dealloc方法是否爆土。

總結(jié):

  1. weak的使用靜態(tài)與動(dòng)態(tài)數(shù)組提高了效率
  2. 動(dòng)態(tài)數(shù)組椭懊,實(shí)質(zhì)在插入數(shù)據(jù)時(shí)判斷實(shí)際個(gè)數(shù)與總個(gè)數(shù)的大小,判斷是否需要擴(kuò)容步势。釋放時(shí)也是如此來(lái)達(dá)到收容氧猬。
  3. 對(duì)于dealloc中的釋放,會(huì)根據(jù)弱引用標(biāo)志位weakly_referenced調(diào)用weak_clear_no_lock進(jìn)行釋放坏瘩。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末盅抚,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子倔矾,更是在濱河造成了極大的恐慌妄均,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哪自,死亡現(xiàn)場(chǎng)離奇詭異丰包,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)壤巷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)邑彪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人胧华,你說(shuō)我怎么就攤上這事寄症。” “怎么了矩动?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵有巧,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我悲没,道長(zhǎng)篮迎,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮柑潦,結(jié)果婚禮上享言,老公的妹妹穿的比我還像新娘峻凫。我一直安慰自己渗鬼,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布荧琼。 她就那樣靜靜地躺著譬胎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪命锄。 梳的紋絲不亂的頭發(fā)上堰乔,一...
    開(kāi)封第一講書(shū)人閱讀 51,488評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音脐恩,去河邊找鬼镐侯。 笑死,一個(gè)胖子當(dāng)著我的面吹牛驶冒,可吹牛的內(nèi)容都是我干的苟翻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼骗污,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼崇猫!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起需忿,我...
    開(kāi)封第一講書(shū)人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤诅炉,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后屋厘,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體涕烧,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年汗洒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了议纯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡仲翎,死狀恐怖痹扇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情溯香,我是刑警寧澤鲫构,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站玫坛,受9級(jí)特大地震影響结笨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一炕吸、第九天 我趴在偏房一處隱蔽的房頂上張望伐憾。 院中可真熱鬧,春花似錦赫模、人聲如沸树肃。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)胸嘴。三九已至,卻和暖如春斩祭,著一層夾襖步出監(jiān)牢的瞬間劣像,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工摧玫, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留耳奕,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓诬像,卻偏偏與公主長(zhǎng)得像屋群,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子颅停,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354

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

  • 我們經(jīng)常寫(xiě)delegate 谓晌,修飾有weak指針,而不用assign癞揉,這是因?yàn)橛脀eak指針不會(huì)纸肉,在delegat...
    充滿(mǎn)活力的早晨閱讀 853評(píng)論 0 10
  • 在runtime中,有四個(gè)數(shù)據(jù)結(jié)構(gòu)非常重要喊熟,分別是SideTables柏肪,SideTable,weak_table_...
    無(wú)忘無(wú)往閱讀 1,162評(píng)論 0 9
  • 準(zhǔn)備工作 請(qǐng)準(zhǔn)備好750.1版本的objc4源碼一份【目前最新的版本】芥牌,打開(kāi)它烦味,找到文章中提到的方法,類(lèi)型壁拉,對(duì)象 ...
    薩繆閱讀 639評(píng)論 1 6
  • 本文不看其他谬俄,只專(zhuān)注于weak的內(nèi)部結(jié)構(gòu)實(shí)現(xiàn)細(xì)節(jié)和源碼解讀,看了網(wǎng)上很多的文章都是貼上一篇open source里...
    凌云壯志幾多愁閱讀 1,475評(píng)論 3 6
  • 被weak修飾的對(duì)象在被釋放的時(shí)候會(huì)發(fā)生什么弃理? 是如何實(shí)現(xiàn)的溃论? 知道sideTable么? 里面的結(jié)構(gòu)可以畫(huà)出來(lái)么...
    塔米爾閱讀 1,805評(píng)論 0 6