sideTable & weakTable 源碼解析 -- 基于最新objc源碼

總的來說:

weak_table 是 SideTable 的一個成員變量虱朵,避免直接操作 weak_table蛮穿。

根據(jù)當前對象指針,做一定偏移驻呐,找到對應的數(shù)組(SideTables)索引灌诅,再根據(jù)索引取>出這個 SideTable 。
你可以理解為key是對象指針含末,value就是 SideTable猜拾。
也就是說,一個對象答渔,對應一個 SideTable关带,一個 SideTable 對應一個 weak_table,
一個weak_table 存儲一個哈希表,key:當前對象宋雏,value:weak 引用

下面的代碼芜飘,都已經(jīng)去掉了無關緊要的,留下的都是重點部分:

首先看一下 SideTables , 實際上內(nèi)部維護了一個Map(key:指針 —— value:SideTable)

array :存儲的就是SideTable 數(shù)組
indexForPointer 方法磨总,這個方法根據(jù)實例的指針地址嗦明,返回它在 array 中的索引,從而>找到 SideTable
由此我們知道:每個實例蚪燕,SideTables 都會幫這個實例存儲一個SideTable

static objc::ExplicitInit<StripedMap<SideTable>> SideTablesMap;

static StripedMap<SideTable>& SideTables() {
    return SideTablesMap.get();
}

StripedMap<T> is a map of [void* : T]

class StripedMap {
    enum { StripeCount = 8 };

    PaddedT array[StripeCount];

    static unsigned int indexForPointer(const void *p) {
        uintptr_t addr = reinterpret_cast<uintptr_t>(p);
        return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
    }

 public:
    T& operator[] (const void *p) { 
        return array[indexForPointer(p)].value; 
    }
    const T& operator[] (const void *p) const { 
        return const_cast<StripedMap<T>>(this)[p]; 
    }
    
#if DEBUG
    StripedMap() {
        // Verify alignment expectations.
        uintptr_t base = (uintptr_t)&array[0].value;
        uintptr_t delta = (uintptr_t)&array[1].value - base;
        ASSERT(delta % CacheLineSize == 0);
        ASSERT(base % CacheLineSize == 0);
    }
#else
    constexpr StripedMap() {}
#endif
};



每個SideTable 都存儲了一個 weak_table 娶牌、引用計數(shù)的map、以及l(fā)ock

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;

    void lock() { slock.lock(); }
    void unlock() { slock.unlock(); }
    void forceReset() { slock.forceReset(); }
};




然后看weak_table:這是一個全局的weak 引用表馆纳。
weak_entries:存儲 weak_entry_t 列表
num_entries :weak 引用的個數(shù)
mask :用來做哈希表的mask

struct weak_table_t {
    weak_entry_t *weak_entries;
    size_t    num_entries;
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};



weak_entry_t: 存儲所有指向?qū)嵗膚eak引用

referent:當前實例對象指針:object point
inline_referrers: weak 變量指針(weak reference ) 集合诗良,引用低于4個的時候,用這個結(jié)構(gòu)存儲
referrers:weak 變量指針(weak reference ) 集合鲁驶,大于4個引用時候用這個存儲

這里使用了 union 來共享內(nèi)存鉴裹,可以節(jié)約內(nèi)存的使用
當 out_of_line_ness == REFERRERS_OUT_OF_LINE 時,使用 referrers 來存儲weak >變量引用
當 out_of_line_ness != REFERRERS_OUT_OF_LINE 時钥弯,使用 inline_referrers 來存>儲weak 變量引用

mask :一般用指針的地址 & mask 來找到對應的數(shù)組索引径荔,而mask 一般為數(shù)組的長度

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];
        };
    };

    bool out_of_line() {
        return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
    }

    weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
        : referent(newReferent)
    {
        inline_referrers[0] = newReferrer;
        for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
            inline_referrers[i] = nil;
        }
    }
};



storeWeak 這個方法用來存儲新的指向當前實例的 weak 變量。
可以看到脆霎,先從 SideTables 拿到對應的 SideTable总处,
然后調(diào)用 weak_register_no_lock 把 newObj 存到了weak_table 里

static id 
storeWeak(id *location, objc_object *newObj)
{
    SideTable *newTable;

    // Acquire locks for old and new values.
    // Order by lock address to prevent lock ordering problems. 
    // Retry if the old value changes underneath us.
 retry:
    
    newTable = &SideTables()[newObj];
    
    newObj = (objc_object *) weak_register_no_lock(&newTable->weak_table, (id)newObj, location,  crashIfDeallocating);

    // Do not set *location anywhere else. That would introduce a race.
    *location = (id)newObj;

    return (id)newObj;
}




weak_entry_for_referent 這個方法就不寫了:實際就是找當前實例對象所屬的 weak_entry_t

這個方法是遍歷 weak_table->weak_entries ,然后檢查 weak_entries[index].referent >== referent
如果找到了睛蛛,就返回 weak_table->weak_entries[index]

接下來我們看另外一個很重要的方法:append_referrer:

/** 
 * Registers a new (object, weak pointer) pair. Creates a new weak
 * object entry if it does not exist.
 * 
 * @param weak_table The global weak table.
 * @param referent The object pointed to by the weak reference.
 * @param referrer The weak pointer address.
 */
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;// 當前的實例對象指針
    objc_object **referrer = (objc_object **)referrer_id;//weak 變量的指針的指針

    // now remember it and where it is being stored
    weak_entry_t *entry;
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        append_referrer(entry, referrer);
    } 
    else {
        weak_entry_t new_entry(referent, referrer);
        weak_grow_maybe(weak_table);
        weak_entry_insert(weak_table, &new_entry);
    }

    return referent_id;
}



把新的weak 變量引用鹦马,加到 entry里:

優(yōu)先使用 inline_referrers 存儲 weak 引用
如果inline_referrers 存儲滿了,則把 inline_referrers 拷貝到 referrers 里玖院,并且以后使用 referrers 來存儲菠红,并標記:out_of_line_ness = REFERRERS_OUT_OF_LINE; 這樣下次默認就使用 referrers 來存儲。

如果存儲的引用數(shù)量超過了 3/4 难菌,則把 referrers 擴容 二倍试溯,再進行存儲。

存儲referrers 的階段郊酒,先把指針做一定偏移遇绞,然后 & mask,找到要存儲在數(shù)組里的位置: index燎窘,
如果這個index 已經(jīng)存儲了值摹闽,那么再次偏移、然后 & mask 褐健,遞歸來找付鹿,直到找到一個空的位置為止澜汤。
最后把 weak 引用存到這個數(shù)組的 index 位置里。

當然舵匾,移除 weak 引用俊抵,和 append 引用是相似的道理,這里就不再贅述了坐梯。

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
    if (! entry->out_of_line()) {
        // Try to insert inline.
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            if (entry->inline_referrers[i] == nil) {
                entry->inline_referrers[i] = new_referrer;
                return;
            }
        }

        // Couldn't insert inline. Allocate out of line.
        weak_referrer_t *new_referrers = (weak_referrer_t *)
            calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
        // This constructed table is invalid, but grow_refs_and_insert
        // will fix it and rehash it.
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            new_referrers[i] = entry->inline_referrers[i];
        }
        entry->referrers = new_referrers;
        entry->num_refs = WEAK_INLINE_COUNT;
        entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;
        entry->mask = WEAK_INLINE_COUNT-1;
        entry->max_hash_displacement = 0;
    }


    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
        return grow_refs_and_insert(entry, new_referrer);
    }
    size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    while (entry->referrers[index] != nil) {
        hash_displacement++;
        index = (index+1) & entry->mask;
        if (index == begin) bad_weak_table(entry);
    }
    if (hash_displacement > entry->max_hash_displacement) {
        entry->max_hash_displacement = hash_displacement;
    }
    weak_referrer_t &ref = entry->referrers[index];
    ref = new_referrer;
    entry->num_refs++;
}
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末徽诲,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子吵血,更是在濱河造成了極大的恐慌谎替,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,948評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蹋辅,死亡現(xiàn)場離奇詭異钱贯,居然都是意外死亡,警方通過查閱死者的電腦和手機晕翠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評論 3 385
  • 文/潘曉璐 我一進店門喷舀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人淋肾,你說我怎么就攤上這事“中希” “怎么了樊卓?”我有些...
    開封第一講書人閱讀 157,490評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長杠河。 經(jīng)常有香客問我碌尔,道長,這世上最難降的妖魔是什么券敌? 我笑而不...
    開封第一講書人閱讀 56,521評論 1 284
  • 正文 為了忘掉前任唾戚,我火速辦了婚禮,結(jié)果婚禮上待诅,老公的妹妹穿的比我還像新娘叹坦。我一直安慰自己,他們只是感情好卑雁,可當我...
    茶點故事閱讀 65,627評論 6 386
  • 文/花漫 我一把揭開白布募书。 她就那樣靜靜地躺著,像睡著了一般测蹲。 火紅的嫁衣襯著肌膚如雪莹捡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,842評論 1 290
  • 那天扣甲,我揣著相機與錄音篮赢,去河邊找鬼。 笑死,一個胖子當著我的面吹牛启泣,可吹牛的內(nèi)容都是我干的涣脚。 我是一名探鬼主播,決...
    沈念sama閱讀 38,997評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼种远,長吁一口氣:“原來是場噩夢啊……” “哼涩澡!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起坠敷,我...
    開封第一講書人閱讀 37,741評論 0 268
  • 序言:老撾萬榮一對情侶失蹤妙同,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后膝迎,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體粥帚,經(jīng)...
    沈念sama閱讀 44,203評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,534評論 2 327
  • 正文 我和宋清朗相戀三年限次,在試婚紗的時候發(fā)現(xiàn)自己被綠了芒涡。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,673評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡卖漫,死狀恐怖费尽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情羊始,我是刑警寧澤簇搅,帶...
    沈念sama閱讀 34,339評論 4 330
  • 正文 年R本政府宣布都弹,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏异旧。R本人自食惡果不足惜莽使,卻給世界環(huán)境...
    茶點故事閱讀 39,955評論 3 313
  • 文/蒙蒙 一凑懂、第九天 我趴在偏房一處隱蔽的房頂上張望腥椒。 院中可真熱鬧,春花似錦敌蚜、人聲如沸桥滨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽该园。三九已至,卻和暖如春帅韧,著一層夾襖步出監(jiān)牢的瞬間里初,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評論 1 266
  • 我被黑心中介騙來泰國打工忽舟, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留双妨,地道東北人淮阐。 一個月前我還...
    沈念sama閱讀 46,394評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像刁品,于是被迫代替她去往敵國和親泣特。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,562評論 2 349