[iOS] weak實現(xiàn)流程

1. weak關(guān)鍵字

weak關(guān)鍵字修飾的對象指針是弱引用,被引用對象的引用計數(shù)不會+1,并在引用對象被釋放的時候自動被設(shè)置為nil。通常用于解決循環(huán)引用問題泞遗,如代理以及及block的使用中,相對會較多的使用到weak席覆。

為了能夠更深入的理解 weak的底層實現(xiàn)史辙,應(yīng)對面試問到關(guān)于weak實現(xiàn)的問題,這次記錄并學(xué)習(xí)一下 weak 實現(xiàn)的源碼佩伤。

2. 實現(xiàn)的大概流程

我們從下面這段代碼開始聊倔,來學(xué)習(xí)一下weak 實現(xiàn)的源碼:

{
    NSObject *obj = [[NSObject alloc] init];
    id __weak obj1 = obj;
}

創(chuàng)建weak引用的時候,會走到runtimeobjc_initWeak的這個方法里生巡,可以通過符號斷點進行驗證耙蔑,源碼如下:

// location 是指向?qū)ο蟮娜踔羔樀牡刂?// newObj 是對象指針
id objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

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

從上面可以看到會走到storeWeak方法中,源碼如下:

/*
*location : weak指針的地址
newObj : 被指向的對象
*/
template <HaveOld haveOld, HaveNew haveNew,
          CrashIfDeallocating crashIfDeallocating>
static id 
storeWeak(id *location, objc_object *newObj)
{
    // 斷言
    ASSERT(haveOld  ||  haveNew);
    if (!haveNew) ASSERT(newObj == nil);

    Class previouslyInitializedClass = nil;
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;

 retry:
    if (haveOld) {
        // 如果weak 指針之前指向過其他對象孤荣,取出這個舊對象
        oldObj = *location;
        // 以舊對象為 key甸陌,從全局 SideTables()中取出舊對象對應(yīng)的 SideTable
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
        // 如果有新值须揣,那么就取出新值對應(yīng)的 SideTable
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

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

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

    // 保證弱引用的對象的 isa 非空,防止弱引用機制和+initialize發(fā)生死鎖
    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);
            previouslyInitializedClass = cls;

            goto retry;
        }
    }

    // 如果之前weak 指針指向了別的對象钱豁,這里清空
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    
    if (haveNew) {
        // 通過 newObj 和 location 生成一個新的 weak_entry_t并插入到 newObj 的弱引用數(shù)組中
        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

        // Set is-weakly-referenced bit in refcount table
        if (newObj  &&  !newObj->isTaggedPointer()) {
            // 設(shè)置被引用對象 isa 的弱引用標(biāo)志位為 YES
            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.
    }
    
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    return (id)newObj;
}

從上面這個方法中我們可以大概了解了圍繞 weak 指針進行的一些操作:

  • 從全局SideTables()中獲取對象所在的 SideTable
  • isa 的非空校驗耻卡,如果isa沒有被初始化,則執(zhí)行class_initialize(cls, (id)newObj)方法
  • 如果weak 指針之前指向了別的對象牲尺,就解除對舊對象的引用
  • 注冊新對象的弱引用
  • 設(shè)置新對象的弱引用標(biāo)志符為 YES

大概的流程圖如下:

1513131184289942.png

3. 散列表的相關(guān)結(jié)構(gòu)

上面storeWeak方法中卵酪,我們看到有一個全局的 SideTables()散列表,我們以此為切入點谤碳,去查看一下關(guān)于weak實現(xiàn)涉及到的整體結(jié)構(gòu)溃卡。

3.1 SideTables()

首先,SideTables()是一個靜態(tài)函數(shù)估蹄,代碼如下:

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

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

函數(shù)體里面調(diào)用了一個全局的靜態(tài)變量SideTablesMapget()方法塑煎,這個靜態(tài)變量保存了所有的SideTable沫换,是objc命名空間下的一個ExplicitInit類臭蚁,它里面實現(xiàn)了get()方法,如下:

Type &get() {
        return *reinterpret_cast<Type *>(_storage);
}

這個get()方法其實返回的就是StripedMap類的實例讯赏。

3.2 StripedMap

StripedMap 是一個以void *p為 key垮兑,PaddedT為 value 的表,實現(xiàn)如下:

template<typename T>
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
    enum { StripeCount = 8 }; // 真機下StripedMap存儲的 SideTable 數(shù)量為 8
#else
    enum { StripeCount = 64 }; // 模擬器下為 64
#endif

    // 對于SideTable的封裝
    struct PaddedT {
        T value alignas(CacheLineSize);
    };

    // 存儲 PaddedT 的散列表
    PaddedT array[StripeCount];

    // 散列函數(shù)漱挎,通過對象地址計算出對應(yīng) PaddedT在數(shù)組中的下標(biāo)
    static unsigned int indexForPointer(const void *p) {
        uintptr_t addr = reinterpret_cast<uintptr_t>(p);
        return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
    }

 public:
    // 取值操作系枪,重寫了[ ]方法,上面提到的&SideTables()[oldObj]會調(diào)用到這個方法
    T& operator[] (const void *p) { 
        return array[indexForPointer(p)].value; 
    }
    const T& operator[] (const void *p) const { 
        return const_cast<StripedMap<T>>(this)[p]; 
    }

   // ... 省略一些方法
};

綜上可以得出 SideTables 的結(jié)構(gòu)實際上如下圖所示:

截屏2021-04-06 下午10.18.01.png

3.3 SideTable

上面介紹StripedMap時磕谅,其內(nèi)部有一個哈希表私爷,表中存儲的是 PaddedT 結(jié)構(gòu)體,結(jié)構(gòu)體的 value 就是 SideTable膊夹,其實現(xiàn)如下:

struct SideTable {
    spinlock_t slock; // 保證原子操作的自旋鎖
    RefcountMap refcnts; // 引用計數(shù)的 hash 表
    weak_table_t weak_table;  // weak 引用全局 hash 表
    // 構(gòu)造函數(shù)
    SideTable() {
        memset(&weak_table, 0, sizeof(weak_table));
    }
    // 析構(gòu)函數(shù)
    ~SideTable() {
        _objc_fatal("Do not delete SideTable.");
    }
};

我們現(xiàn)在把重點放在weak_table_t weak_table上衬浑。
SideTable結(jié)構(gòu)圖如下:

截屏2021-04-06 下午10.27.27.png

3.4 weak_table_t

上面我們可以看到SideTable 結(jié)構(gòu)體中有一個weak_table_t結(jié)構(gòu)體類型的成員變量,實現(xiàn)如下:

// 全局弱引用表
struct weak_table_t {
    weak_entry_t *weak_entries; // hash 數(shù)組放刨,用來存儲弱引用對象相關(guān)信息的 weak_entry_t
    size_t    num_entries; // hash數(shù)組中元素的個數(shù)
    uintptr_t mask; // hash 數(shù)組的長度(并不是實際的存儲個數(shù))-1工秩,主要用來參與哈希函數(shù)
    uintptr_t max_hash_displacement; // 最大哈希偏移值
};

結(jié)構(gòu)圖如下:


截屏2021-04-06 下午10.39.51.png
3.5 weak_entry_t

上面的weak_table_t中可以看到其內(nèi)部有一個weak_entry_t的結(jié)構(gòu)體數(shù)組,這就是其內(nèi)部維護的哈希表进统,我們現(xiàn)在來看下weak_entry_t結(jié)構(gòu)體的實現(xiàn)助币,如下:

struct weak_entry_t {
    DisguisedPtr<objc_object> referent; // 弱引用的對象
    union {
        // 弱引用數(shù)量大于 4 個用到的結(jié)構(gòu)體
        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;
        };
        // 弱引用數(shù)量不大于 4 個用到的結(jié)構(gòu)體
        struct {
            // out_of_line_ness field is low bits of inline_referrers[1]
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];  // 存儲指向該對象的弱引用數(shù)組
        };
    };

    // 判斷是否用的是 referrers 來存儲弱引用指針
    bool out_of_line() {
        return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
    }
    // 覆蓋老數(shù)據(jù)
    weak_entry_t& operator=(const weak_entry_t& other) {
        memcpy(this, &other, sizeof(other));
        return *this;
    }
    // 構(gòu)造方法
    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;
        }
    }
};

從上面的代碼實現(xiàn)中可以看出,weak_entry_t結(jié)構(gòu)體存放的是某個對象的所有弱引用指針螟碎,存放所有弱引用指針使用的是一個聯(lián)合體眉菱,如果弱引用指針的數(shù)量不超過 4 個就用inline_referrers存儲,否則用referrers存儲掉分。

結(jié)構(gòu)圖如下:


截屏2021-04-06 下午10.43.50.png
3.6 weak_referrer_t

weak_entry_t用于存放所有指向某個對象的 weak 指針地址俭缓,類型是weak_entry_t迈螟,實現(xiàn)如下:

typedef DisguisedPtr<objc_object *> weak_referrer_t;
3.7 結(jié)構(gòu)圖總結(jié)
截屏2021-04-06 下午10.50.31.png

4. 代碼結(jié)合結(jié)構(gòu)圖具體分析

我們還從??上面提到的storeWeak方法中進行具體分析,代碼如下尔崔,標(biāo)注的一些點在下面會進行具體分析:

template <HaveOld haveOld, HaveNew haveNew,
          CrashIfDeallocating crashIfDeallocating> 
storeWeak(id *location, objc_object *newObj)

{
    //校驗舊對象和新對象必須存其一
    ASSERT(haveOld  ||  haveNew);
    //校驗如果haveNew=true答毫,newObj不能為nil
    if (!haveNew) ASSERT(newObj == nil);

    Class previouslyInitializedClass = nil;
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;

 retry:
    if (haveOld) {
        //如果weak 指針指向舊值,就取出舊值
        oldObj = *location;
        //以舊對象地址為 key取出舊的SideTable
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
     // 取出對應(yīng)新的SideTable
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }
    
    //上鎖
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
    //校驗季春,如果舊值對不上 goto retry
    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        goto retry;
    }

    //保證弱引用對象的isa非空洗搂,防止弱引用機制和+initialize 發(fā)生死鎖
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            //如果class沒有初始化發(fā)送+initialized消息
            class_initialize(cls, (id)newObj);

            previouslyInitializedClass = cls;
            //到這里class肯定已經(jīng)初始化了,在走一遍
            goto retry;
        }
    }

    if (haveOld) {
    //<<1>>如果weak 指針之前指向了其他對象载弄,在這里清空
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    if (haveNew) {
    //通過newObj和location生成一個新的weak_entry_t并插入到newObj的弱引用數(shù)組中(weak_entries)
    //<<2>>
        newObj = (objc_object *)
        weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
        crashIfDeallocating);

        // Set is-weakly-referenced bit in refcount table.
        if (newObj  &&  !newObj->isTaggedPointer()) {
           //<<3>>  設(shè)置 isa 的標(biāo)志位
           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.
    }
    
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    return (id)newObj;
}
4.1 解除 weak 指針之前舊的指向

方法調(diào)用如下:

// 參數(shù) 1:weak 指針指向?qū)ο笏诘娜止1?// 參數(shù) 2:weak 指針指向的舊對象
// 參數(shù) 3:weak 指針的地址
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);

weak_unregister_no_lock方法主要是清除存儲在weak_entry_t 中的weak_refrerrer_t耘拇,如果刪除后weak_entry_t中的數(shù)組為空,則將整個weak_entry_tweak_table_t中移除宇攻, 方法源碼如下:

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_ptr的地址

    weak_entry_t *entry;

    if (!referent) return;
    //<<1>> 查找referent對應(yīng)的weak_entry_t
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        //<<2>>如果entry存在惫叛,刪除entry中的referrer
        remove_referrer(entry, referrer);
        bool empty = true;
        // 判斷entry的動態(tài)數(shù)組referrers中是否有值
        if (entry->out_of_line()  &&  entry->num_refs != 0) {
            empty = false;
        }
        else {
            //判斷entry的定長數(shù)組inline_referrers中是否有值
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                if (entry->inline_referrers[i]) {
                    empty = false; 
                    break;
                }
            }
        }
        //如果都是空的將entry從weak_table移除
        if (empty) {
            //<<3>>移除entry
            weak_entry_remove(weak_table, entry);
        }
    }
4.1.1 查找referent對應(yīng)的weak_entry_t

具體方法實現(xiàn)如下:

// weak_table 當(dāng)前對象對應(yīng)的SideTable中的弱引用表
// referent 當(dāng)前對象
static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
    ASSERT(referent);
    // 獲取所有的weak_entry_t數(shù)組
    weak_entry_t *weak_entries = weak_table->weak_entries;

    if (!weak_entries) return nil;
    
    // 通過對對象指針的哈希方法生成的值與 weak_table->mask 進行 BITMASK 操作得到一個起始值
    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];
}
  • 每次遍歷如果沒有在weak_entries中找到referent對應(yīng)的weak_entry_t,就對index + 1 再進行BITMASK 操作逞刷,遍歷一次就記錄一次嘉涌,直到大于max_hash_displacement 最大偏移值,返回 nil夸浅,說明當(dāng)前對象在weak_tableweak_entries中沒有對應(yīng)的weak_entry_t仑最,也就是說沒有弱引用
  • 如果某個weak_entry_treferent和參數(shù)referent相等,說明找到了帆喇,返回這個weak_entry_t
4.1.2 刪除 entry 中的 referrer

在上一步中我們找到了存儲當(dāng)前對象弱引用的weak_entry_t警医,現(xiàn)在我們就需要從weak_entry_t中的referrers或者inline_referrers中刪除掉之前的弱引用,源碼實現(xiàn)如下:

// entry: 當(dāng)前對象對應(yīng)的 weak_entry_t
// old_referrer : 弱引用指針的地址
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
  
    if (! entry->out_of_line()) {
      // 從 entry 的定長(長度為 4)數(shù)組inline_referrers中查找 old_referrer坯钦,如果有則置為 nil
        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;
    }

    // 從動態(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;
        }
    }
    // 置為 nil预皇,并且對應(yīng)的數(shù)量-1
    entry->referrers[index] = nil;
    entry->num_refs--;
}
  • 如果使用的是定長數(shù)組(數(shù)量為 4 個),那么就對inline_referrers進行遍歷查找婉刀,如果存在弱引用指針old_referrer吟温,則設(shè)為 nil
  • 如果使用的是動態(tài)數(shù)組,那么就對referrers進行查找路星,如果存在溯街,設(shè)為nil,并將引用數(shù)量-1洋丐,查找方法和上面??那一步類似
4.1.3 如果 entry中的數(shù)組為空呈昔,則從 weak_table_t中刪除 entry

源碼實現(xiàn)如下:

static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
{
    // 如果使用的是動態(tài)數(shù)組,則釋放動態(tài)數(shù)組的內(nèi)存
    if (entry->out_of_line()) free(entry->referrers);
    // 以 entry 為起始地址的前sizeof(*entry)個字節(jié)區(qū)域清零
    bzero(entry, sizeof(*entry));
    // 全局 weak_table_t中的弱引用對象數(shù)量-1
    weak_table->num_entries--;
    // 收縮表大小
    weak_compact_maybe(weak_table);
}
  • weak_compact_maybe(weak_table) 收縮表大小
    實現(xiàn)如下:
static void weak_compact_maybe(weak_table_t *weak_table)
{
    // #define TABLE_SIZE(entry) (entry->mask ? entry->mask + 1 : 0)
    size_t old_size = TABLE_SIZE(weak_table);

    // 如果 weak_table 的表大小占用超過 1024 個字節(jié)友绝,并且 1/16比弱引用的對象的數(shù)量還多就收縮表的大小堤尾,使其不超過原來的 1/2
    if (old_size >= 1024  && old_size / 16 >= weak_table->num_entries) {
        weak_resize(weak_table, old_size / 8);
        // leaves new table no more than 1/2 full
    }
}
  • weak_resize源碼
static void weak_resize(weak_table_t *weak_table, size_t new_size)
{
    // 獲取舊的大小
    size_t old_size = TABLE_SIZE(weak_table);
    
    weak_entry_t *old_entries = weak_table->weak_entries;
    // 使用新的大小創(chuàng)建新的new_entries
    weak_entry_t *new_entries = (weak_entry_t *)
        calloc(new_size, sizeof(weak_entry_t));
    // 將weak_table的成員變量重新賦值
    weak_table->mask = new_size - 1;
    weak_table->weak_entries = new_entries;
    weak_table->max_hash_displacement = 0;
    weak_table->num_entries = 0;  // restored by weak_entry_insert below
    
    // 如果old_entries還有值,則進行遍歷重新放入 weak_table 新的weak_entries中
    if (old_entries) {
        weak_entry_t *entry;
        weak_entry_t *end = old_entries + old_size;
        for (entry = old_entries; entry < end; entry++) {
            if (entry->referent) {
                // 插入這一步在下面會講到
                weak_entry_insert(weak_table, entry);
            }
        }
        // 釋放舊的
        free(old_entries);
    }
}
4.2 生成新的 weak_entry_t 插入到weak_table_t中的 weak_entryies中

之前storeWeak中相關(guān)的代碼如下:

...
newObj = (objc_object *)
        weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
        crashIfDeallocating);
...

我們具體來看下weak_register_no_lock方法的源碼迁客,實現(xiàn)如下:

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;
    
    // 如果為 nil郭宝,或者是TaggedPointer辞槐,則直接 return referent_id
    if (!referent  ||  referent->isTaggedPointer()) return referent_id;

    // 判斷當(dāng)前對象是否正在釋放或是否支持弱引用
    bool deallocating;
    if (!referent->ISA()->hasCustomRR()) {
        deallocating = referent->rootIsDeallocating();
    }
    else {
        BOOL (*allowsWeakReference)(objc_object *, SEL) = 
            (BOOL(*)(objc_object *, SEL))
            object_getMethodImplementation((id)referent, 
                                           @selector(allowsWeakReference));
        if ((IMP)allowsWeakReference == _objc_msgForward) {
            return nil;
        }
        deallocating =
            ! (*allowsWeakReference)(referent, @selector(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;
        }
    }

  
    weak_entry_t *entry;
   // 判斷如果對象已經(jīng)在 weak_table 中存在弱引用記錄,就在原來的 entry 上追加
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        append_referrer(entry, referrer);
    } 
    // 如果不存在則創(chuàng)建一個新的 entry粘室,加入到 weak_table中
    else {
        weak_entry_t new_entry(referent, referrer);
        weak_grow_maybe(weak_table);
        weak_entry_insert(weak_table, &new_entry);
    }
    return referent_id;
}
4.2.1 weak_entry_t 中添加新的 weak_referrer_t

append_referrer具體實現(xiàn)如下:

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
    // 如果使用的是定長數(shù)組榄檬,找到為 nil 的位置,賦值即可
    if (! entry->out_of_line()) {
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            if (entry->inline_referrers[i] == nil) {
                entry->inline_referrers[i] = new_referrer;
                return;
            }
        }

       // 如果放不下了衔统,就申請創(chuàng)建動態(tài)數(shù)組new_referrers
        weak_referrer_t *new_referrers = (weak_referrer_t *)
            calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
        // 然后將之前定長數(shù)組的弱引用賦值給new_referrers
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            new_referrers[i] = entry->inline_referrers[i];
        }
        // 設(shè)置 entry 相關(guān)的變量的值
        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;
    }

    ASSERT(entry->out_of_line());

    // 如果引用數(shù)量超過表大小的 3/4就自動擴容
    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
        return grow_refs_and_insert(entry, new_referrer);
    }
    // 在 referrers 中找到一個值為 nil 的 weak_referrer_t鹿榜,用新的弱引用對其賦值,并將引用數(shù)量+1
    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++;
}
  • grow_refs_and_insert 對某個對象的弱引用表擴容并進行插入
__attribute__((noinline, used))
static void grow_refs_and_insert(weak_entry_t *entry, 
                                 objc_object **new_referrer)
{
    ASSERT(entry->out_of_line());
    
    // 獲取舊表的大小
    size_t old_size = TABLE_SIZE(entry);
    // 如果之前有舊表锦爵,則擴容為之前的 2 倍舱殿,否則為 8
    size_t new_size = old_size ? old_size * 2 : 8;
    
    // 獲取當(dāng)前對象所有弱引用指針的數(shù)量
    size_t num_refs = entry->num_refs;
    // 獲取舊的數(shù)組
    weak_referrer_t *old_refs = entry->referrers;
    // mask 賦值
    entry->mask = new_size - 1;
    // 使用新的 size 創(chuàng)建referrers,并給 entry 賦值
    entry->referrers = (weak_referrer_t *)
        calloc(TABLE_SIZE(entry), sizeof(weak_referrer_t));
    // 剛開始都為 0
    entry->num_refs = 0;
    entry->max_hash_displacement = 0;
    
    // 將老的弱引用指針地址放到新的里邊
    for (size_t i = 0; i < old_size && num_refs > 0; i++) {
        if (old_refs[i] != nil) {
            // 進行插入
            append_referrer(entry, old_refs[i]);
            num_refs--;
        }
    }
    // 最后將新的弱引用指針地址進行插入
    append_referrer(entry, new_referrer);
    if (old_refs) free(old_refs);
}
4.2.2 new_entry(referent, referrer) 創(chuàng)建新的 entry

構(gòu)造方法實現(xiàn)如下:

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

從上面代碼中可以看出险掀,剛開始對象沒有弱引用指針沪袭,使用的是定長數(shù)組inline_referrers

4.2.3 weak_grow_maybe weak_table擴容

實現(xiàn)代碼如下:

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

    // 如果超過 3/4 則進行擴容,如果之前有樟氢,則為原來的 2 倍冈绊,否則為 64
    if (weak_table->num_entries >= old_size * 3 / 4) {
        weak_resize(weak_table, old_size ? old_size*2 : 64);
    }
}

這里也是調(diào)用weak_resize方法,將weak_table_t進行擴容嗡害,上面??是進行收縮焚碌。

4.2.4 weak_table_t 中插入 weak_entry_t

實現(xiàn)代碼如下:

static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
{
    weak_entry_t *weak_entries = weak_table->weak_entries;
    ASSERT(weak_entries != nil);
    // 計算出要插入的位置
    size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    while (weak_entries[index].referent != nil) {
        index = (index+1) & weak_table->mask;
        if (index == begin) bad_weak_table(weak_entries);
        hash_displacement++;
    }
    // 進行賦值畦攘,數(shù)量自增
    weak_entries[index] = *new_entry;
    weak_table->num_entries++;
    // 對最大max_hash_displacement偏移值進行賦值霸妹,這也是查找時遍歷的臨界點
    if (hash_displacement > weak_table->max_hash_displacement) {
        weak_table->max_hash_displacement = hash_displacement;
    }
}

4.3 設(shè)置弱引用的標(biāo)志位

這一步就是標(biāo)記這個對象有弱引用,在這個對象 dealloc的時候知押,將所有的弱引用釋放掉叹螟,實現(xiàn)代碼如下:

inline void
objc_object::setWeaklyReferenced_nolock()
{
 retry:
    // 獲取對象的 isa 指針
    isa_t oldisa = LoadExclusive(&isa.bits);
    isa_t newisa = oldisa;
    if (slowpath(!newisa.nonpointer)) {
        ClearExclusive(&isa.bits);
        sidetable_setWeaklyReferenced_nolock();
        return;
    }
    // 如果對象isa 的弱引用標(biāo)志位已經(jīng)是 true 了,則不進行操作
    if (newisa.weakly_referenced) {
        ClearExclusive(&isa.bits);
        return;
    }
    // 弱引用標(biāo)志位設(shè)為 true
    newisa.weakly_referenced = true;
    //     //如果oldisa.bits和newisa.bits不相等返回NO台盯,繼續(xù)tery里面的內(nèi)容罢绽,這時候newisa.weakly_referenced已經(jīng)是true了,所以return

    if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
}

5. 弱引用指針的釋放

- (void)dealloc {
    _objc_rootDealloc(self);
}

?

void _objc_rootDealloc(id obj)
{
    ASSERT(obj);

    obj->rootDealloc();
}

?

inline void objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}

?

id object_dispose(id obj)
{
    if (!obj) return nil;

    objc_destructInstance(obj);    
    free(obj);

    return nil;
}

?

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        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)) {
        // Slow path for non-pointer isa with weak refs and/or side table data.
        clearDeallocating_slow();
    }

    assert(!sidetable_present());
}

?

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();
}

終于來到了關(guān)鍵的方法weak_clear_no_lock静盅,實現(xiàn)如下:

 */
void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    objc_object *referent = (objc_object *)referent_id;
    // 獲取當(dāng)前對象所在的weak_entry_t
    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    if (entry == nil) {
        /// XXX shouldn't happen, but does with mismatched CF/objc
        //printf("XXX no entry for clear deallocating %p\n", referent);
        return;
    }

    // zero out references
    weak_referrer_t *referrers;
    size_t count;
    
    if (entry->out_of_line()) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    // 遍歷entry的 referrers 數(shù)組良价,將弱引用指針全置為 nil 
    for (size_t i = 0; i < count; ++i) {
        objc_object **referrer = referrers[i];
        if (referrer) {
            if (*referrer == referent) {
                *referrer = nil;
            }
            else if (*referrer) {
                _objc_inform("__weak variable at %p holds %p instead of %p. "
                             "This is probably incorrect use of "
                             "objc_storeWeak() and objc_loadWeak(). "
                             "Break on objc_weak_error to debug.\n", 
                             referrer, (void*)*referrer, (void*)referent);
                objc_weak_error();
            }
        }
    }
    // 從 weak_table中移除當(dāng)前對象對應(yīng)的 entry
    weak_entry_remove(weak_table, entry);
}

上面流程主要是針對于weak實現(xiàn)的介紹,其中對于 isa.nonpointer 還有 isa的標(biāo)志位以及 Tagged Pointer沒有進行過多介紹蒿叠,之后文章會詳細進行介紹明垢。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市市咽,隨后出現(xiàn)的幾起案子痊银,更是在濱河造成了極大的恐慌,老刑警劉巖施绎,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件溯革,死亡現(xiàn)場離奇詭異贞绳,居然都是意外死亡,警方通過查閱死者的電腦和手機致稀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門冈闭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人抖单,你說我怎么就攤上這事拒秘。” “怎么了臭猜?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵躺酒,是天一觀的道長。 經(jīng)常有香客問我蔑歌,道長羹应,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任次屠,我火速辦了婚禮园匹,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘劫灶。我一直安慰自己裸违,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布本昏。 她就那樣靜靜地躺著供汛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪涌穆。 梳的紋絲不亂的頭發(fā)上怔昨,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機與錄音宿稀,去河邊找鬼趁舀。 笑死,一個胖子當(dāng)著我的面吹牛祝沸,可吹牛的內(nèi)容都是我干的矮烹。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼罩锐,長吁一口氣:“原來是場噩夢啊……” “哼奉狈!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起唯欣,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤嘹吨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后境氢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蟀拷,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡碰纬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了问芬。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片悦析。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖此衅,靈堂內(nèi)的尸體忽然破棺而出强戴,到底是詐尸還是另有隱情,我是刑警寧澤挡鞍,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布骑歹,位于F島的核電站,受9級特大地震影響墨微,放射性物質(zhì)發(fā)生泄漏道媚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一翘县、第九天 我趴在偏房一處隱蔽的房頂上張望最域。 院中可真熱鬧,春花似錦锈麸、人聲如沸镀脂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽薄翅。三九已至,卻和暖如春虑省,著一層夾襖步出監(jiān)牢的瞬間匿刮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工探颈, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人训措。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親美旧。 傳聞我的和親對象是個殘疾皇子凡简,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,446評論 2 348

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