iOS Weak 原理雜談

關(guān)于 weak 的實(shí)現(xiàn)原理,網(wǎng)上已經(jīng)有各種詳細(xì)的文章了纷纫,大多貼了一段代碼涕蚤,然后根據(jù)代碼解釋流程,這里隨便找了一篇供參考:iOS weak的實(shí)現(xiàn)原理

就我個人而言戳玫,看完這些文章感覺還是理解不深,特別是數(shù)據(jù)結(jié)構(gòu)方面的靠抑,比如 weak 表量九,可能很多人都只有一個大概的認(rèn)知:weak 指針存放在weak表中适掰,這是一種哈希表颂碧,對象作為key,weak指針數(shù)組作為value类浪,然后就沒有了载城,沒有更清晰的認(rèn)知,這里根據(jù)源碼調(diào)試結(jié)果费就,主要談?wù)剋eak表相關(guān)的結(jié)構(gòu)诉瓦。

以下面代碼為例子:

NSObject *obj = [NSObject new];
__weak id weakObj = obj;

通過 objc_initWeak 最后走到關(guān)鍵方法 storeWeak(這里去掉部分代碼,保留關(guān)鍵邏輯)

static id storeWeak(id *location, objc_object *newObj)
{
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;
 retry:
    if (haveOld) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

    // Clean up old value, if any.
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // Assign new value, if any.
    if (haveNew) {
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected

        // Set is-weakly-referenced bit in refcount table.
        if (!newObj->isTaggedPointerOrNil()) {
            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.
    }
    return (id)newObj;
}

先看代碼 newTable = &SideTables()[newObj];
這里的 newObj 就是我們上面的NSObject 對象 obj力细,這段代碼可以看成如下三行:

// newTable = &SideTables()[newObj];

SideTable * newTable = nil;
StripedMap *map = &SideTables();
newTable = map[newObj];

其中 StripedMap 是一個全局的哈希表(我們常說的weak表就是這個)睬澡,這是一個C++類,通過重寫下標(biāo)操作符[]來實(shí)現(xiàn)key-value存取眠蚂,其內(nèi)部維護(hù)了一個數(shù)組煞聪,大小是 64,也就是最多可以存儲 64 個SideTable逝慧,這里以對象地址為key昔脯,返回一個SideTable啄糙,內(nèi)部原理大概是這樣的:

// StripedMap *map = &SideTables();
// newTable  = map[newObj];
unsigned int index = someHashCode(newObj)%64;
newTable = map.array[index];

哈希表的大小是64,哈希沖突時采用數(shù)組存儲沖突的對象云稚,再看 SideTable 結(jié)構(gòu)(簡化版):

struct SideTable {
    spinlock_t slock;         // 自旋鎖
    RefcountMap refcnts;      // 引用計(jì)數(shù)
    weak_table_t weak_table;  // weak 指針
};
struct weak_table_t {
    weak_entry_t *weak_entries;
};

其中 weak_table 用來存放weak指針相關(guān)的信息隧饼,內(nèi)部是一個數(shù)組weak_entries,數(shù)組的元素是 weak_entry_t静陈,這個 weak_entry_t 是最后weak指針存放的位置燕雁,一個對象對應(yīng)一個 weak_entry_t,這里的數(shù)組 weak_entries 就是用來存放不同對象的weak指針鲸拥,再來看 weak_entry_t:

struct weak_entry_t {
    // 存儲對象 obj
    DisguisedPtr<objc_object> referent;
    union {
        struct {
            // 存儲weak指針 weakObj
            weak_referrer_t *referrers;
        };
        struct {
            // 存儲weak指針 weakObj
            // WEAK_INLINE_COUNT = 4贵白;
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };
};

其中 referent 用來存放對象,referrers 崩泡,inline_referrers 用來存儲 weak指針禁荒,一個對象可以有多個weak指針指向它,默認(rèn)情況下角撞,weak指針存放在 inline_referrers 中呛伴,當(dāng) inline_referrers 存滿后(超過4個)就會轉(zhuǎn)到 referrers 中動態(tài)分配內(nèi)存存儲。

NSObject *obj = [NSObject new];
__weak id weakObj = obj;

用偽偽偽代碼表示上面代碼weak指針的存儲過程:

storeWeak(id * weakObj, id obj) {
    // 獲取全局 StripedMap 哈希表對象
    StripedMap *map = &SideTables();
    // 獲取 obj 所映射位置的 SideTable
    SideTable * newTable = map[obj];
    // 獲取 weak指針數(shù)組
    weak_table_t table_t = newTable->weak_table;
    
    // 創(chuàng)建 entry_t 存儲 weakObj 和 obj 的對應(yīng)關(guān)系
    weak_entry_t entry_t;
    entry_t.referent = obj;
    entry_t.inline_referrers[0] = weakObj;
    // 插入
    table_t.weak_entries->insert(entry_t);

    // SideTable * newTable = map[obj];
    // newTable->weak_table.weak_entries->insert(entry_t);
}

上述就是存儲一個weak指針的基本過程谒所,剔除了海量細(xì)節(jié)热康,比如已存在obj weak指針的處理,但是只要清楚了數(shù)據(jù)結(jié)構(gòu)劣领,其他方面沒有太多的黑魔法姐军,都是正常的操作。

至于weak指針的釋放尖淘,在 obj dealloc 方法里奕锌,通過一系列調(diào)用,最后會調(diào)用到 weak_clear_no_lock 中村生,核心代碼如下(刪刪刪):

void weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    objc_object *referent = (objc_object *)referent_id;
    // 找到對象 referent_id(就是上面的obj) 對應(yīng)的 weak_entry_t
    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    if (entry == nil) { return;}
    // zero out references
    weak_referrer_t *referrers;
    size_t count;
    // 前面說過惊暴,對象 referent_id  的 weak指針超過4個就會轉(zhuǎn)移到 referrers 中
    if (entry->out_of_line()) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else { // 否則在存在 inline_referrers 中
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    
    for (size_t i = 0; i < count; ++i) {
        // 找到所有的 weak 指針
        objc_object **referrer = referrers[i];
        if (referrer) {
            if (*referrer == referent) {
                // weak 指針置為 nil
                *referrer = nil;
            }
        }
    }
    // 從 weak_table 的 weak_entries 數(shù)組中移除 entry
    weak_entry_remove(weak_table, entry);
}

weak 的原理大體上就是這樣,需要注意的是對于 TaggedPointer 對象趁桃,不走這一套流程辽话,也就是不存儲 TaggedPointer 對象的 weak 指針,因?yàn)?TaggedPointer 對象是存儲在指針本身里的卫病,是在棧區(qū)油啤,不是堆區(qū),內(nèi)存由系統(tǒng)自動管理蟀苛。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末益咬,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子屹逛,更是在濱河造成了極大的恐慌础废,老刑警劉巖汛骂,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異评腺,居然都是意外死亡帘瞭,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門蒿讥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蝶念,“玉大人,你說我怎么就攤上這事芋绸∶窖常” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵摔敛,是天一觀的道長廷蓉。 經(jīng)常有香客問我,道長马昙,這世上最難降的妖魔是什么桃犬? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮行楞,結(jié)果婚禮上攒暇,老公的妹妹穿的比我還像新娘。我一直安慰自己子房,他們只是感情好形用,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著证杭,像睡著了一般田度。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上躯砰,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天每币,我揣著相機(jī)與錄音,去河邊找鬼琢歇。 笑死,一個胖子當(dāng)著我的面吹牛梦鉴,可吹牛的內(nèi)容都是我干的李茫。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼肥橙,長吁一口氣:“原來是場噩夢啊……” “哼魄宏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起存筏,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤宠互,失蹤者是張志新(化名)和其女友劉穎味榛,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體予跌,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡搏色,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了券册。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片频轿。...
    茶點(diǎn)故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖烁焙,靈堂內(nèi)的尸體忽然破棺而出航邢,到底是詐尸還是另有隱情,我是刑警寧澤骄蝇,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布膳殷,位于F島的核電站,受9級特大地震影響九火,放射性物質(zhì)發(fā)生泄漏秽之。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一吃既、第九天 我趴在偏房一處隱蔽的房頂上張望考榨。 院中可真熱鬧,春花似錦鹦倚、人聲如沸河质。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽掀鹅。三九已至,卻和暖如春媒楼,著一層夾襖步出監(jiān)牢的瞬間乐尊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工划址, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留扔嵌,地道東北人。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓夺颤,卻偏偏與公主長得像痢缎,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子世澜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評論 2 354

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

  • 在iOS開發(fā)過程中独旷,會經(jīng)常使用到一個修飾詞weak,使用場景大家都比較清晰,避免出現(xiàn)對象之間的強(qiáng)強(qiáng)引用而造成對象不...
    風(fēng)緊扯呼閱讀 486評論 0 2
  • weak是iOS開發(fā)中很常見的知識點(diǎn)嵌洼,大家都知道weak是一個修飾詞案疲,作用是對修飾的對象弱引用,在對象被釋放的時候...
    xxxxxxxx_123閱讀 709評論 0 2
  • 內(nèi)存概述 內(nèi)存是用來存啥的麻养? 內(nèi)存布局 哈希表 垃圾回收(GC) IOS內(nèi)存管理機(jī)制 MRC & ARC T...
    zhiziZ閱讀 865評論 0 4
  • 夜鶯2517閱讀 127,720評論 1 9
  • 版本:ios 1.2.1 亮點(diǎn): 1.app角標(biāo)可以實(shí)時更新天氣溫度或選擇空氣質(zhì)量褐啡,建議處女座就不要選了,不然老想...
    我就是沉沉閱讀 6,891評論 1 6