runtime之weak指針的實(shí)現(xiàn)

我們經(jīng)常寫delegate ,修飾有weak指針偎谁,而不用assign,這是因?yàn)橛脀eak指針不會(huì)纲堵,在delegate對(duì)象釋放的時(shí)候不會(huì)引起崩潰巡雨,而assign會(huì)引起崩潰。(野指針)席函。這里就有個(gè)疑問铐望,為什么用weak不會(huì)引起崩潰呢?

我的源碼地址

weak指針存放地址

我們查看源碼文件NSObject.mm文件,我們看見有許多帶有weak的api正蛙。

id
objc_initWeak(id *location, id newObj)
id
objc_storeWeakOrNil(id *location, id newObj)
id
objc_storeWeak(id *location, id newObj)
static id 
storeWeak(id *location, objc_object *newObj)
id
objc_initWeakOrNil(id *location, id newObj)
void
objc_destroyWeak(id *location)
id
objc_loadWeakRetained(id *location)
id
objc_loadWeak(id *location)
void
objc_copyWeak(id *dst, id *src)
void
objc_moveWeak(id *dst, id *src)

其實(shí)這些api就是用來操作weak指針的
從這些api中我們我們首先要找初始化函數(shù)
從apple對(duì)每個(gè)函數(shù)的注釋上我們能看出來督弓,程序在啟動(dòng)點(diǎn)時(shí)候調(diào)用初始化函數(shù)id
objc_initWeak(id location, id newObj)。
我在空白程序中加入
objc_initWeak* 信號(hào)斷點(diǎn)乒验,運(yùn)行日志如下愚隧。

image.png

我們能看出來,程序在啟動(dòng)的時(shí)候在main函數(shù)之后(在main出打的斷點(diǎn)锻全,先執(zhí)行在執(zhí)行objc_initWeak)初始化的該函數(shù)狂塘。

函數(shù)入口objc_initWeak

找到函數(shù)入口了,那我們就需要借助源碼往下看鳄厌,看看objc_initWeak 函數(shù)初始化到底干了啥事情

/** 
 * Initialize a fresh weak pointer to some object location. 
 * It would be used for code like: 
 *
 * (The nil case) 
 * __weak id weakPtr;
 * (The non-nil case) 
 * NSObject *o = ...;
 * __weak id weakPtr = o;
 * 
 * This function IS NOT thread-safe with respect to concurrent 
 * modifications to the weak variable. (Concurrent weak clear is safe.)
 *
 * @param location Address of __weak ptr. 
 * @param newObj Object ptr. 
 */
id
objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<false/*old*/, true/*new*/, true/*crash*/>
        (location, (objc_object*)newObj);
}

我認(rèn)為有必要把a(bǔ)pple的注釋也貼出來荞胡,供英文好的人看看么

1 判斷newObj 是不是空,空就返回
2 調(diào)用storeWeak 函數(shù) 了嚎。

看這里關(guān)鍵是函數(shù)storeWeak 的調(diào)用了

storeWeak(id *location, objc_object *newObj)
{
    assert(HaveOld  ||  HaveNew);
    if (!HaveNew) assert(newObj == nil);

    Class previouslyInitializedClass = nil;
    id oldObj;
    SideTable *oldTable;
    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:
    /// oldObj 是若引用指針
    if (HaveOld) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    
    /// 對(duì)象表
    if (HaveNew) {
        ///這個(gè)獲取對(duì)象沒看懂
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

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

    // Prevent a deadlock between the weak reference machinery
    // and the +initialize machinery by ensuring that no 
    // weakly-referenced object has an un-+initialized isa.
    
    if (HaveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        ///指向的類 沒有實(shí)例化就實(shí)例化
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);
            _class_initialize(_class_getNonMetaClass(cls, (id)newObj));

            // If this class is finished with +initialize then we're good.
            // If this class is still running +initialize on this thread 
            // (i.e. +initialize called storeWeak on an instance of itself)
            // then we may proceed but it will appear initializing and 
            // not yet initialized to the check above.
            // Instead set previouslyInitializedClass to recognize it on retry.
            previouslyInitializedClass = cls;

            goto retry;
        }
    }

    // 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);
        // weak_register_no_lock returns nil if weak store should be rejected

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

參數(shù):HaveOld = false 泪漂,HaveNew =true,CrashIfDeallocating=true, 還有兩個(gè)外界傳入的location和newObj。五個(gè)

準(zhǔn)備知識(shí)

1我們?cè)谏厦娴暮瘮?shù)中有這么段代碼 oldTable = &SideTables()[oldObj]; 這是c++ 的寫法歪泳,我們要搞懂這是在干么才能理解這段代碼窖梁。這段代碼調(diào)用了 下面函數(shù)

static StripedMap<SideTable>& SideTables() {
    return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}

reinterpret_cast 是強(qiáng)制類型轉(zhuǎn)換,將SideTableBuf 轉(zhuǎn)換成StripedMap<SideTable>類型夹囚。
SideTableBuf 是什么呢?

// We cannot use a C++ static initializer to initialize SideTables because
// libc calls us before our C++ initializers run. We also don't want a global 
// pointer to this struct because of the extra indirection.
// Do it the hard way.
alignas(StripedMap<SideTable>) static uint8_t SideTableBuf[sizeof(StripedMap<SideTable>)];

我們搞明白c++ 中的StripedMap<SideTable>邀窃。我們知道是StripedMap 是類荸哟,< SideTable> 給StripedMap 類傳入的模板,我們看 StripedMap 有好多T 其實(shí)T 在這里應(yīng)該換成SideTable 瞬捕。
再回來看我們就知道其實(shí)就是分配了一個(gè)StripedMap 對(duì)象大小的內(nèi)存而已鞍历。內(nèi)存大小和StripedMap一樣,那么我們就可以把這塊內(nèi)存當(dāng)做StripedMap對(duì)象使用了肪虎。
&SideTables()[oldObj] 劣砍,其實(shí)就是StripedMap 方法調(diào)用

 T& operator[] (const void *p) { 
       return array[indexForPointer(p)].value; 
   }
 static unsigned int indexForPointer(const void *p) {
       uintptr_t addr = reinterpret_cast<uintptr_t>(p);
       return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
   }

這里我們能看出來,我們調(diào)用static unsigned int indexForPointer(const void *p) 函數(shù)扇救,這個(gè)函數(shù)首先獲取p的指針刑枝,對(duì)addr進(jìn)行操作符操作,再進(jìn)行取余操作,獲取的值命名為index。(這里其實(shí)就是將p經(jīng)過一定的運(yùn)算挣郭,獲取一個(gè)大小在0~64 之間的值)
我們?cè)購(gòu)腁rray中獲取下標(biāo)值index收捣。這里其實(shí)就是獲取到一個(gè)結(jié)構(gòu)體對(duì)象SideTable。

見圖


image.png
  • 紅色代表類名
  • 粉紅代表類中的項(xiàng)
  • 黃色代表的數(shù)組中的item

調(diào)用順序

1 檢測(cè)參數(shù)鄙币,HaveOld 和 HaveNew 不能同時(shí)是false
2 HaveNew = false 税迷,那么newObj 必須是nil淤翔。
3 聲明幾個(gè)變量蚂夕,Class previouslyInitializedClass 迅诬, id oldObj; SideTable oldTable; SideTable newTable;
4 這里要是 HaveOld = YES ,從location的地方獲取對(duì)象賦值給oldObj,獲取oldTable 表地址
要是HaveNew = YES, 獲取newObj 處 的SideTable 表地址
5 根據(jù)HaveOld 和 HaveNew 值分別鎖定對(duì)應(yīng)的SideTable 表
6 如果HaveOld = yes,并且oldObj 不在位置
location 所在地址婿牍,那么就重新執(zhí)行4侈贷。這里保證oldObj 一定在
location 所在位置。別的執(zhí)行7
7.如果HaveNew 并且還有newObj 對(duì)象牍汹,執(zhí)行8操作铐维,否則執(zhí)行9
8獲取下newObj 對(duì)象isa,如果class沒有實(shí)例化慎菲,那么實(shí)例化下嫁蛇。返回到4重新執(zhí)行。別的執(zhí)行9
9 如果HaveOld = yes露该, 那么調(diào)用void
weak_unregister_no_lock(weak_table_t weak_table, id referent_id,
id referrer_id)函數(shù)睬棚。(
具體分析這個(gè)函數(shù)在后面**),將對(duì)象從 oldTable 表中刪除
10 如果 HaveNew = yes,調(diào)用id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, bool crashIfDeallocating) 解幼,將值寫入newTable中抑党,并且檢查newObj是不是isTaggedPointer,設(shè)置newObj的值標(biāo)記weak位置撵摆。并且把location 存入值newObj底靠。
11 解鎖 表返回

上面兩個(gè)函數(shù)比較重要我們看看對(duì)象是如何從SideTable 存入和刪除的。我們先看看SideTable 表結(jié)構(gòu)特铝,在看如何刪除和存入的

siderTable結(jié)構(gòu)比較簡(jiǎn)單

SideTable.png

1 有三個(gè)變量暑中,spinlock_t slock; 鎖,weak_table_t weak_table;weak指針表
weak_table_t 結(jié)構(gòu)體鲫剿,四個(gè)成員鳄逾,這里主要看 weak_entry_t *weak_entries;
weak_entry_t 結(jié)構(gòu)如圖

#define WEAK_INLINE_COUNT 4
struct weak_entry_t {
    DisguisedPtr<objc_object> referent;
    union {
        struct {
            weak_referrer_t *referrers;
            uintptr_t        out_of_line : 1;
            uintptr_t        num_refs : PTR_MINUS_1;
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        struct {
            // out_of_line=0 is LSB of one of these (don't care which)
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };
};

我們看看weak對(duì)象如何存入上述結(jié)構(gòu)的

/** 
 * 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;

    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;
    ///真實(shí)地址 全局map
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        append_referrer(entry, referrer);
    } 
    else {
        weak_entry_t new_entry;
        new_entry.referent = referent;
        new_entry.out_of_line = 0;
        new_entry.inline_referrers[0] = referrer;
        for (size_t i = 1; i < WEAK_INLINE_COUNT; i++) {
            new_entry.inline_referrers[i] = nil;
        }
        
        weak_grow_maybe(weak_table);
        weak_entry_insert(weak_table, &new_entry);
    }

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

    return referent_id;
}

  • 參數(shù): weak_table_t 需要存入對(duì)象表,referent_id 需要存入對(duì)象指針(weak指針指向的真實(shí)對(duì)象地址)灵莲,referent_id 引用的地址(weak對(duì)象指針地址)雕凹,crashIfDeallocating 是否打印日志。
  • 調(diào)用順序
  1. 獲取真是對(duì)象地址政冻,獲取weak對(duì)象指針地址枚抵。
    2.如果真實(shí)對(duì)象是nil,或者是tag指針明场,返回引用的地址(因?yàn)門aggedPoint 同樣的值就一個(gè)地址)
    image.png

    image.png

    3判斷真實(shí)對(duì)象是否釋放了俄精,沒有被釋放,檢測(cè)下是否允許weak指針引用榕堰。(這里不到存入數(shù)據(jù)竖慧,可以不仔細(xì)看)
    4 聲明變量 weak_entry_t entry;
    5 調(diào)用 weak_entry_for_referent 函數(shù) (
    這個(gè)函數(shù)是在weak_table_t 表中查找真是對(duì)象對(duì)應(yīng)的weak_entry_t嫌套,沒找到返回nil,找到返回weak_entry_t ),返回不是nil圾旨,執(zhí)行6 踱讨,返回nil 執(zhí)行7
    6 . 查詢結(jié)果不是nil,調(diào)用append_referrer(
    我們將weak指針寫入 真實(shí)對(duì)象對(duì)應(yīng)的weak_entry_t 結(jié)構(gòu)體中

    7 .查詢結(jié)果是nil 砍的,我們我們創(chuàng)建一個(gè)新的weak_entry_t痹筛。(
    這里我們知道了weak_entry_t 結(jié)構(gòu)體的成員變量的含義了,referent 指向weak對(duì)象廓鞠,inline_referrers[0] 指向?qū)ο蟮刂?/em>
    8 調(diào)用weak_grow_maybe 查詢并且擴(kuò)展表(
    查詢表大小是否夠了帚稠,不夠需要擴(kuò)展表
    9 調(diào)用weak_entry_insert 將 entry 存入表中。(
    數(shù)據(jù)存入表中*)

我們看看上面步驟中的5步具體調(diào)用

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 index = hash_pointer(referent) & weak_table->mask;
    size_t hash_displacement = 0;
    ///判斷相等不相等查找過程
    while (weak_table->weak_entries[index].referent != referent) {
        index = (index+1) & weak_table->mask;
        hash_displacement++;
        if (hash_displacement > weak_table->max_hash_displacement) {
            return nil;
        }
    }
    
    return &weak_table->weak_entries[index];
}
  1. 要是weak_table_t 中的weak_entries 變量是nil 床佳,返回nil(沒有創(chuàng)建weak_entry_t 結(jié)構(gòu)體)
    2.根據(jù)真實(shí)對(duì)象指針referent 與weak_table_t 的mask 獲取一個(gè)位置index滋早。
    3.查詢 從index 的位置開始循環(huán)查詢weak_entry_t 對(duì)象是否包含真實(shí)對(duì)象的指針,要是所有的的weak_entry_t 都不包含砌们,返回nil(說明還沒有弱指針指向這個(gè)對(duì)象)杆麸。有就返回這個(gè)weak_entry_t 結(jié)構(gòu)體。

接著我們看看查詢到weak_entry_t 調(diào)用append_referrer 如何存入弱指針的

/** 
 * Add the given referrer to set of weak pointers in this entry.
 * Does not perform duplicate checking (b/c weak pointers are never
 * added to a set twice). 
 *
 * @param entry The entry holding the set of weak pointers. 
 * @param new_referrer The new weak pointer to be added.
 */

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 = 1;
        entry->mask = WEAK_INLINE_COUNT-1;
        entry->max_hash_displacement = 0;
    }
///一定大于1
    assert(entry->out_of_line);
// 4  >=3
    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
        return grow_refs_and_insert(entry, new_referrer);
    }
    
    ///
    size_t index = w_hash_pointer(new_referrer) & (entry->mask);
    size_t hash_displacement = 0;
    while (entry->referrers[index] != NULL) {
        index = (index+1) & entry->mask;
        hash_displacement++;
    }
    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++;
}

看這個(gè)函數(shù)浪感,我們要明白下weak_entry_t 結(jié)構(gòu)體每個(gè)成員變量的作用才行昔头,這里分析源碼獲取的。

#define WEAK_INLINE_COUNT 4
struct weak_entry_t {
    DisguisedPtr<objc_object> referent;
    union {
        struct {
            weak_referrer_t *referrers;
            uintptr_t        out_of_line : 1;
            uintptr_t        num_refs : PTR_MINUS_1;
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        struct {
            // out_of_line=0 is LSB of one of these (don't care which)
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };
};
  • DisguisedPtr<objc_object> referent; 真實(shí)對(duì)象地址影兽,相當(dāng)于key
  • weak_referrer_t *referrers; 當(dāng)弱引用對(duì)象多余4個(gè)的時(shí)候揭斧,存入弱引用地址
  • uintptr_t out_of_line : 1; 0 代表 我們使用weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; 存入若引用地址,1 代表我們是用weak_referrer_t *referrers; 指針存入地址峻堰。 當(dāng)弱引用的數(shù)量大于4 改指針變成1
  • uintptr_t num_refs : PTR_MINUS_1; 記錄當(dāng)前集合存入了多少個(gè)弱引用指針
  • uintptr_t mask; 代表最多可以存入多少個(gè)若引用指針未蝌。
  • uintptr_t max_hash_displacement; 從index位置偏移的位置。
  • weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; 當(dāng)若引用指針少于四個(gè)存入在該變量中

append_referrer 調(diào)用分析

1 首先判斷weak_entry_t 的out_of_line 變量是否是0茧妒,不是0 執(zhí)行4,是0 執(zhí)行2.
2 檢查 weak_entry_t 的inline_referrers 指針是否存入四個(gè)值左冬,沒有則將對(duì)這個(gè)真實(shí)對(duì)象的weak指針new_referrer存入到inline_referrers 中桐筏。否則執(zhí)行3
3 要是weak_entry_t 結(jié)構(gòu)體的inline_referrers指針存滿了,那么我們重新分配空間new_referrers拇砰,類型是weak_referrer_t梅忌,將inline_referrers 數(shù)據(jù)存入到new_referrers 指針對(duì)應(yīng)的地址,然后將weak_entry_t 結(jié)構(gòu)體的referrers指向new_referrers 除破,同時(shí)牧氮,將weak_entry_t 結(jié)構(gòu)體的num_refs 賦值為WEAK_INLINE_COUNT(宏定義,數(shù)值4)瑰枫,將out_of_line 更新為1 (說明指針存在結(jié)構(gòu)體weak_entry_t的referrers中)踱葛,weak_entry_t 的mask 是WEAK_INLINE_COUNT -1.(因?yàn)橄聵?biāo)是0開始的)丹莲,這時(shí)候還沒有便宜,weak_entry_t的max_hash_displacement = 0尸诽;
4 到這里說明對(duì)該對(duì)象的weak指針已經(jīng)多于4個(gè)了甥材。判斷要是已經(jīng)存入的指針是總體指針的75%。說明存儲(chǔ)weak指針的空間不足性含,需要重新分配內(nèi)存洲赵,這里調(diào)用grow_refs_and_insert方法實(shí)現(xiàn)。要是空間充足商蕴,那么調(diào)用 5
5 .獲取new_referrer 的在 weak_entry_t mask范圍內(nèi)的index,查找該位置是否已經(jīng)被存入值了叠萍。存入了值,就index +1 绪商,hash_displacement 累加1苛谷,繼續(xù)執(zhí)行5 。直到找到空的位置為止部宿。
6 將hash_displacement 寫入到weak_entry_t 結(jié)構(gòu)體的max_hash_displacement
7 將數(shù)據(jù)存入想應(yīng)的index抄腔,讓num_refs 計(jì)數(shù)加1。

我們接著看看第四步調(diào)用的grow_refs_and_insert 方法理张,擴(kuò)展weak指針空間

__attribute__((noinline, used))
static void grow_refs_and_insert(weak_entry_t *entry, 
                                 objc_object **new_referrer)
{
    assert(entry->out_of_line);
///4
    size_t old_size = TABLE_SIZE(entry);
    ///8
    size_t new_size = old_size ? old_size * 2 : 8;
/// 4
    size_t num_refs = entry->num_refs;
    /// 4
    weak_referrer_t *old_refs = entry->referrers;
    ///7
    entry->mask = new_size - 1;
    /// 8
    entry->referrers = (weak_referrer_t *)
        calloc(TABLE_SIZE(entry), sizeof(weak_referrer_t));
    
    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--;
        }
    }
    // Insert
    append_referrer(entry, new_referrer);
    if (old_refs) free(old_refs);
}

調(diào)用邏輯分析

1.獲取weak_entry_t 結(jié)構(gòu)體的mask 數(shù)量赫蛇,賦值給old_size
2.獲取new_size的大小,是old_size大小的兩倍雾叭。
3.獲取 weak_entry_t 結(jié)構(gòu)體的num_refs 悟耘,意思是當(dāng)前存入的指針數(shù)量,賦值給num_refs

  1. 獲取weak_entry_t 結(jié)構(gòu)體 referrers 织狐,保存在old_refs 中暂幼,
  2. 重新寫入weak_entry_t 的mask大小,值是new_size-1.(比原來擴(kuò)大兩倍了)
    6.給weak_entry_t 的referrers 重新分配空間移迫,數(shù)量是new_size 大小個(gè)weak_referrer_t空間
    7 因?yàn)樾路峙淇臻g旺嬉,設(shè)置weak_entry_t結(jié)構(gòu)體的num_refs 是0,weak_entry_t結(jié)構(gòu)體的max_hash_displacement 是0
    8.調(diào)用append_referrer 將老的指針寫入到weak_entry_t結(jié)構(gòu)體的referrers 中
    9 將新的new_referrer 寫入到weak_entry_t結(jié)構(gòu)體的referrers 中

回到函數(shù)weak_register_no_lock中,我們分析下當(dāng)如果在weak_table_t 表中沒有找到weak_entry_t結(jié)構(gòu)體的時(shí)候厨埋,我們調(diào)用的函數(shù)

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) {
        weak_resize(weak_table, old_size ? old_size*2 : 64);
    }
}

1 獲取weak_table_t 的mask
2 查看weak_table_t 的空間大小如果不足75%.那么擴(kuò)展空間兩倍邪媳。實(shí)現(xiàn)和weak_entry_t 擴(kuò)展對(duì)象空間一樣。不做詳細(xì)講解了荡陷。

看到這里我想大概weak指針如何存入的應(yīng)該明白了雨效。我們繪制下weak指針的存入結(jié)構(gòu)。
見圖


image.png

存入順序是
1.根據(jù)對(duì)象obj獲取stripedMap 中的 所對(duì)應(yīng)的SideTable 結(jié)構(gòu)體
2.根據(jù)對(duì)象obj 獲取SideTable 中weak_table_t 結(jié)構(gòu)體下 對(duì)應(yīng)的weak_entry_t結(jié)構(gòu)體废赞,所有關(guān)于obj對(duì)象的弱指針都存放在該weak_entry_t結(jié)構(gòu)體里面徽龟。
3.將weak指針存入到weak_entry_t 下的weak_referrer_t 中

接下來我們看看如何獲取weak指針

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;

    if (!referent) return;

    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        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;
                }
            }
        }

        if (empty) {
            weak_entry_remove(weak_table, entry);
        }
    }

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

調(diào)用順序如下

  1. referent指向真是對(duì)象
    2.referrer 指向weak指針對(duì)象
    3 如果referent 是nil 就返回
    4 從weak_table_t 表中根據(jù)真實(shí)對(duì)象(referent)獲取到對(duì)應(yīng)的weak_entry_t 結(jié)構(gòu)體,沒有就直接結(jié)束了唉地。
  2. 找到了真是對(duì)象對(duì)應(yīng)的weak_entry_t結(jié)構(gòu)體据悔,那么調(diào)用remove_referrer传透, 刪除 weak指針。(具體怎么刪除下面分析)屠尊。
    6這里判斷weak_entry_t 的out_of_line =1 并且weak_entry_t的num_refs不是0旷祸,說明還有weak指針。不要?jiǎng)h除這個(gè)真實(shí)對(duì)象對(duì)應(yīng)的weak_entry_t結(jié)構(gòu)體
    7要是weak_entry_t 的out_of_line=0 讼昆,我們知道對(duì)象存入在weak_entry_t 的inline_referrers中托享,檢查weak_entry_t 的inline_referrers 的是否是nil,如果是空那么就刪除掉真實(shí)對(duì)象(referent)對(duì)應(yīng)的weak_entry_t結(jié)構(gòu)體

看到這里其實(shí)就是我們存入的逆順序刪除而已浸赫。
上面的函數(shù)只是真正的刪除了weak_entry_t 結(jié)構(gòu)體而沒有對(duì)weak指針刪除闰围,這里我們?cè)倏纯凑嬲齱eak指針的刪除。

static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
    if (! entry->out_of_line) {
        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;
    }

    size_t index = w_hash_pointer(old_referrer) & (entry->mask);
    size_t hash_displacement = 0;
    while (entry->referrers[index] != old_referrer) {
        index = (index+1) & entry->mask;
        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--;
}
  1. 判斷weak_entry_t結(jié)構(gòu)體 的out_of_line = 0 ,那么我們就判斷weak_entry_t 結(jié)構(gòu)體的inline_referrers中是否存在weak指針既峡,存入將該區(qū)域設(shè)置為nil
  2. 要是weak_entry_t結(jié)構(gòu)體 的out_of_line = 1 羡榴,那么我們獲取下weak指針對(duì)應(yīng)的在 weak_referrer_t *referrers 的index位置。
    3.查詢index 處的weak指針是否和刪除的指針相等运敢,不相等index累加校仑,hash_displacement累加,檢查hash_displacement 是否已經(jīng)超過weak_entry_t 結(jié)構(gòu)體的max_hash_displacement的值传惠,超過就結(jié)束迄沫,沒有那么繼續(xù)執(zhí)行3.
    4.查詢到 需要?jiǎng)h除對(duì)象的位置,將該位置設(shè)置nil
    5 將weak_entry_t 結(jié)構(gòu)體的num_refs 減去1.(刪除掉一個(gè)指針了)

我們這里已經(jīng)把weak指針的存入和刪除都分析完畢了卦方。

這里還有一點(diǎn)沒說明羊瘩,就是當(dāng)真實(shí)對(duì)象釋放掉了。如何清理對(duì)象所在的weak指針盼砍。
當(dāng)對(duì)象釋放掉的時(shí)候尘吗,會(huì)調(diào)用到對(duì)象的dealloc方法。dealloc方法中會(huì)調(diào)用到這個(gè)方法.void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 將對(duì)象所對(duì)應(yīng)的weak表清除掉

void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    objc_object *referent = (objc_object *)referent_id;

    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;
    }
    
    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_entry_remove(weak_table, entry);
}

這個(gè)函數(shù)實(shí)現(xiàn)很簡(jiǎn)單浇坐,不做介紹了睬捶。

純手工自己摸索寫的,哪里不對(duì)請(qǐng)給個(gè)指正近刘。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末擒贸,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子跌宛,更是在濱河造成了極大的恐慌,老刑警劉巖积仗,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疆拘,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡寂曹,警方通過查閱死者的電腦和手機(jī)哎迄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門回右,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人漱挚,你說我怎么就攤上這事翔烁。” “怎么了旨涝?”我有些...
    開封第一講書人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵蹬屹,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我白华,道長(zhǎng)慨默,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任弧腥,我火速辦了婚禮厦取,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘管搪。我一直安慰自己虾攻,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開白布更鲁。 她就那樣靜靜地躺著霎箍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪岁经。 梳的紋絲不亂的頭發(fā)上朋沮,一...
    開封第一講書人閱讀 52,268評(píng)論 1 309
  • 那天,我揣著相機(jī)與錄音缀壤,去河邊找鬼樊拓。 笑死,一個(gè)胖子當(dāng)著我的面吹牛塘慕,可吹牛的內(nèi)容都是我干的筋夏。 我是一名探鬼主播,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼图呢,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼条篷!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蛤织,我...
    開封第一講書人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤赴叹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后指蚜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體乞巧,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年摊鸡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了绽媒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蚕冬。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖是辕,靈堂內(nèi)的尸體忽然破棺而出囤热,到底是詐尸還是另有隱情,我是刑警寧澤获三,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布旁蔼,位于F島的核電站,受9級(jí)特大地震影響石窑,放射性物質(zhì)發(fā)生泄漏牌芋。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一松逊、第九天 我趴在偏房一處隱蔽的房頂上張望躺屁。 院中可真熱鬧,春花似錦经宏、人聲如沸犀暑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)耐亏。三九已至,卻和暖如春沪斟,著一層夾襖步出監(jiān)牢的瞬間广辰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工主之, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留择吊,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓槽奕,卻偏偏與公主長(zhǎng)得像几睛,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子粤攒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

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