__weak 修飾符

SideTables

SideTables 是全局表漆改,管理著對(duì)象的引用計(jì)數(shù)和weak引用指針互婿,每個(gè)對(duì)象在此表中都有對(duì)應(yīng)的一個(gè) SideTable崔涂,讓我們來(lái)看看 SideTables 源碼定義

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

雖然看不懂十兢,但從源碼的定義可以看出 SideTables 是通過(guò) StripedMap 來(lái)實(shí)現(xiàn)的勋颖,我們來(lái)看一下它的實(shí)現(xiàn)

template<typename T>
class StripedMap {

    enum { CacheLineSize = 64 };

#if TARGET_OS_EMBEDDED // 嵌入式
    enum { StripeCount = 8 };
#else
    enum { StripeCount = 64 };
#endif

    struct PaddedT {
        T value alignas(CacheLineSize);
    };

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

從上可以看出侨赡,StripedMap 的容量大小為:StripeCount = 64蓖租,通過(guò) indexForPointer 函數(shù)分配在 StripedMap的下標(biāo)。public 中的應(yīng)該是讀存方法吧(猜測(cè)羊壹,哈哈蓖宦。。)油猫,可以看到其和 Map 集合一樣稠茂。

SideTable

SideTable 的定義如下

typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap;

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;
    ...
};
  • slock:是用于在對(duì) SideTable 操作時(shí),對(duì) SideTable 加鎖防止其他訪問(wèn)。
  • refcnts:對(duì)象的引用計(jì)數(shù)器睬关,存儲(chǔ)著對(duì)象被引用的記錄诱担。
  • weak_table_t: 存放弱變量引用
DisguisedPtr
class DisguisedPtr {
    uintptr_t value;
    
    // 對(duì)指針進(jìn)行偽裝
    static uintptr_t disguise(T* ptr) {
        return -(uintptr_t)ptr;
    }
    // 恢復(fù)至原指針
    static T* undisguise(uintptr_t val) {
        return (T*)-val;
    }
};

DisguisedPtr 對(duì)指針進(jìn)行偽裝的類,將指針強(qiáng)轉(zhuǎn)為 uintptr_t (unsigned long)類型的負(fù)值电爹,這樣類似 leaks 這樣的查內(nèi)存泄漏的工具便無(wú)法跟蹤到對(duì)象蔫仙。

weak_table_t
struct weak_table_t {
    weak_entry_t *weak_entries;
    size_t    num_entries;
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};
  • weak_entries:存放對(duì)象與弱引用對(duì)象指針映射的弱引用條目數(shù)組
  • num_entries:弱引用條目總數(shù)
  • mask:可存儲(chǔ)弱引用條目的容量
  • max_hash_displacement:最大哈希偏移值
weak_entry_t
#define WEAK_INLINE_COUNT 4
#define REFERRERS_OUT_OF_LINE 2

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 {
            
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };
};

weak_entry_t 是一個(gè)弱引用條目,其映射了引用對(duì)象和其被弱引用的指針丐箩,referent 便是引用對(duì)象摇邦,union(聯(lián)合體) 里存放著弱引用該對(duì)象的指針,union 里面的多個(gè)成員變量共享同一內(nèi)存空間屎勘。union 中有兩個(gè)結(jié)構(gòu)體都是存儲(chǔ)弱引用對(duì)象指針的集合施籍。第一個(gè)結(jié)構(gòu)體中 referrers 是一個(gè)可進(jìn)行擴(kuò)容的集合,而第二個(gè)結(jié)構(gòu)體中 inline_referrers 是一個(gè)容量為 4 的數(shù)組概漱,weak_entry_t 默認(rèn)使用 inline_referrers 來(lái)保存弱引用指針丑慎,當(dāng)此數(shù)組容量滿后,會(huì)使用 referrers 接管保存工作犀概。out_of_line_ness 便是描述存儲(chǔ)的弱引用指針是否超出 inline_referrers 的容量立哑。

__weak 原理

NSString *aa = @"aa";
__weak NSString *test = aa;

上面代碼在編譯時(shí),模擬的代碼如下:

NSString *aa;
aa = @"aa"
NSString *test;
objc_initWeak(&obj, aa);
objc_destoryWeak(&obj);

__weak 變量創(chuàng)建

__weak 變量的創(chuàng)建入口是 objc_initWeak 這個(gè)函數(shù)姻灶,其實(shí)現(xiàn)是:

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

如果 __weak 變量被賦予的對(duì)象是 nil 那么,將 __weak 變量置 nil诈茧,進(jìn)入 objc_destoryWeak 銷毀函數(shù)产喉。storeWeak 函數(shù)是一個(gè)更新弱變量的函數(shù),此函數(shù)有點(diǎn)長(zhǎng)敢会,我們分段講述:

// 如果 HaveOld 為 true曾沈,則表明變量需要清理,變量可能為nil
// 如果 HaveNew 為 true鸥昏,則表明有一個(gè)新值將賦予變量塞俱,這個(gè)新值可能為 nil
// 如果 CrashIfDeallocating 為 true,則表明新值 newObj 是釋放了的對(duì)象(并不是說(shuō) newObj 為 nil)或者是一個(gè)不支持弱引用的對(duì)象吏垮。
// 如果 CrashIfDeallocating 為 false障涯,則將新值 newObj 置nil 并 *location 弱變量賦值為 nil。 
template <bool HaveOld, bool HaveNew, bool 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;

    // Acquire locks for old and new values.
    // 為新舊值獲取鎖膳汪。
    // Order by lock address to prevent lock ordering problems. 
    // 按鎖地址排序以防止鎖排序問(wèn)題
    // Retry if the old value changes underneath us.
    // 如果下面的舊值發(fā)生更改唯蝶,請(qǐng)重試。
 retry:
    if (HaveOld) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (HaveNew) {
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

聲明一個(gè) previouslyInitializedClass 保存先前初始化的類遗嗽,聲明一個(gè)舊值對(duì)象 oldObj粘我,一新一舊兩個(gè) SideTable(散列表)。從 objc_initWeak 傳入的 HaveOld 為 false痹换,HaveNew 為 true征字,因此將 oldTable 賦值為 nil都弹,從 SideTables 獲取 newObj 的 SideTable 賦值給 newTable。兩個(gè)散列表處理好了后匙姜,因?yàn)楫?dāng)前是 __weak 變量的創(chuàng)建缔杉,處理的是新值,所以下面只給出新值有關(guān)的處理代碼

// 給新舊散列表加鎖
SideTable::lockTwo<HaveOld, HaveNew>(oldTable, newTable);

// 通過(guò)確保沒(méi)有弱引用對(duì)象具有未初始化的isa搁料,防止弱引用機(jī)制和初始化機(jī)制之間的死鎖或详。
if (HaveNew  &&  newObj) {
    Class cls = newObj->getIsa();
    if (cls != previouslyInitializedClass  &&  
        !((objc_class *)cls)->isInitialized()) 
    {
        SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);
        _class_initialize(_class_getNonMetaClass(cls, (id)newObj));

        // 如果這個(gè)類完成了+initialize,那最好郭计。如果這個(gè)類仍在這個(gè)線程上運(yùn)行+initialize(即+initialize霸琴,調(diào)用storeWeak,在其自身的實(shí)例上)昭伸,我們可以繼續(xù)梧乘,但是將顯示為初始化和尚未初始化作為上述檢查,設(shè)置 previouslyInitializedClass 以在重試時(shí)識(shí)別它
        previouslyInitializedClass = cls;

        goto retry;
    }
}

此步驟為確保弱引用對(duì)象 newObj 初始化了庐杨,首先通過(guò)獲取 newObj 的 isa 指針獲取它的類选调,然后判斷它的類是否初始化了,如果沒(méi)有灵份,便打開(kāi)新舊散列表的鎖仁堪,獲取 newObj 的元類發(fā)送 +initialize 消息進(jìn)行初始化。下面是 storeWeak 函數(shù)最后一部分:


// Assign new value, if any.
if (HaveNew) {
    // 弱引用注冊(cè)失敗便返回 nil
    newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table, (id)newObj, location, CrashIfDeallocating);
    
    // 設(shè)置refcount表中的弱引用位
    if (newObj  &&  !newObj->isTaggedPointer()) {
        newObj->setWeaklyReferenced_nolock();
    }

    // 不要在其他地方設(shè)置 *location填渠,否則會(huì)有沖突弦聂。
    *location = (id)newObj;
}
// 打開(kāi)新舊散列表的鎖
SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);

// 弱引用處理完畢,返回新值
return (id)newObj;

首先氛什,通過(guò) weak_register_no_lock 函數(shù)將 __weak 變量對(duì) newObj 的弱引用注冊(cè)到 newObj的散列表的弱引用表中莺葫,如果注冊(cè)成功則設(shè)置 newObj 的refcount表中的 __weak 變量對(duì)其的引用為弱引用,然后將新值賦給 __weak 變量枪眉。最后捺檬,所有操作完成了,打開(kāi)新舊散列表的鎖贸铜,返回新值賦給 __weak 變量堡纬。

弱引用注冊(cè)

__weak 變量引用對(duì)象時(shí),需要將 __weak 變量的弱引用注冊(cè)到被引用對(duì)象的弱引用表中萨脑,這一操作便由 weak_register_no_lock 函數(shù)完成隐轩。此函數(shù)的實(shí)現(xiàn)我們分兩部分程呈現(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;

    if (!referent  ||  referent->isTaggedPointer()) return referent_id;

    // ensure that the referenced object is viable
    // 確保引用的對(duì)象是可用的
    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;
        }
    }

上部分是為了確保弱引用的對(duì)象 referent(newObj對(duì)象)支持被弱引用。首先判斷引用對(duì)象 referent 的 isa 中是否有自定義 retain 和 release 實(shí)現(xiàn)渤早,如果沒(méi)有职车,則調(diào)用 rootIsDeallocating() 函數(shù)檢查 referent 是否在析構(gòu)(即是否被釋放)。

如果referent 的 isa 中有自定義 retain 和 release 實(shí)現(xiàn),首先會(huì)獲取 referent 中 SEL_allowsWeakReference 方法的實(shí)現(xiàn)悴灵,如果獲取的是 _objc_msgForward 消息轉(zhuǎn)發(fā)函數(shù)扛芽,那么表明該引用對(duì)象不支持弱引用。反之积瞒,便發(fā)送 SEL_allowsWeakReference 消息去判斷該對(duì)象是否支持弱引用川尖,如果支持則表示 referent 引用對(duì)象不在析構(gòu)。

如果該引用對(duì)象在析構(gòu)并且 crashIfDeallocating(控制引用對(duì)象析構(gòu)是否需crash)為true茫孔,則crash叮喳。如果 crashIfDeallocating 為 false,則返回 nil 表示注冊(cè)弱引用失敗缰贝。

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

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

    return referent_id;
}

這下半部分就是將 __weak 變量的弱引用指針存儲(chǔ)到被引用對(duì)象 newObj 的弱引用表中馍悟,完成注冊(cè)。首先通過(guò) weak_entry_for_referent 函數(shù)去查找 newObj 對(duì)應(yīng)的 SideTable 的 weak_table 表中的對(duì)應(yīng) newObj 的弱引用條目 entry剩晴。如果不存在 entry锣咒,則用 newobj 和 __weak變量指針生成一個(gè)新的弱引用條目 new_entry。接下來(lái)執(zhí)行 weak_grow_maybe 函數(shù)看 weak_table 是否需要擴(kuò)容赞弥。

weak_grow_maybe 和 weak_resize
#define TABLE_SIZE(entry) (entry->mask ? entry->mask + 1 : 0)

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.
    // 如果當(dāng)前條目數(shù)已滿容量的 3/4 則允許擴(kuò)容
    // 如果是初次的話擴(kuò)容 64毅整,之后以 2 倍增加
    if (weak_table->num_entries >= old_size * 3 / 4) {
        weak_resize(weak_table, old_size ? old_size*2 : 64);
    }
}

如果 newObj 是第一次被引用,那么其對(duì)應(yīng)的 weak_table 的容量 mask 應(yīng)為 0绽左,則 old_size = 0悼嫉, weak_table 的弱引用條目總數(shù)自然也為 0。滿足擴(kuò)容條件妇菱,因此初次擴(kuò)容為 64承粤,執(zhí)行 weak_resize(weak_table, 64)。

weak_resize 是對(duì) weak_table 擴(kuò)容的函數(shù)闯团,其實(shí)現(xiàn)如下:

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;
    weak_entry_t *new_entries = (weak_entry_t *)
        calloc(new_size, sizeof(weak_entry_t));

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

創(chuàng)建一個(gè) weak_entry_t 實(shí)例 old_entries 保存 weak_table 弱引用表中的弱引用條目列表,然后創(chuàng)建一個(gè) 64 格的新弱引用條目列表仙粱,接著更新設(shè)置 weak_table 的容量大小房交、弱引用條目列表、最大哈希位移數(shù)伐割、條目總數(shù)候味。最后,如果舊弱引用條目列表 old_entries 存在數(shù)據(jù)隔心,則將舊條目列表的數(shù)據(jù)插入 weak_table 新擴(kuò)容的條目列表中并釋放舊條目列表白群。

擴(kuò)容完后,便開(kāi)始將新創(chuàng)建的條目插入 weak_table 的條目列表 weak_entries 中硬霍。

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

    weak_entries[index] = *new_entry;
    weak_table->num_entries++;

    if (hash_displacement > weak_table->max_hash_displacement) {
        weak_table->max_hash_displacement = hash_displacement;
    }
}

通過(guò) hash_pointer(new_entry->referent) 和 weak_table->mask 的與運(yùn)算決定新弱引用條目在 weak_entries 的初始下標(biāo)帜慢,如果 weak_entries 中該下標(biāo)中沒(méi)被用,則將 new_entry 存放在此處,weak_table 的 num_entries 自增長(zhǎng) 1粱玲。

如果該初始下標(biāo)中已被存放了條目躬柬,則循環(huán)將 hash_pointer() 計(jì)算的 hash值 + 1 再次與 weak_table->mask 進(jìn)行“與運(yùn)算”并且哈希位移數(shù)自增加 1,如果沒(méi)找到可存儲(chǔ)的位置則會(huì)執(zhí)行 bad_weak_table 報(bào) “This may be a runtime bug or a memory error somewhere else.” 錯(cuò)誤抽减。

如果找到可用的位置允青,則 new_entry 將存放到 weak_entries 中。最后卵沉,如果哈希位移數(shù)大于 weak_table 中存儲(chǔ)的最大哈希位移數(shù)颠锉,則更新 weak_table 中的 max_hash_displacement 值為 hash_displacement。 到此處史汗,弱引用的注冊(cè)也就完成了琼掠。

append_referrer

如果 newObj 是一個(gè)被其他變量弱引用的對(duì)象,那么能通過(guò) weak_entry_for_referent 函數(shù)找到 newObj 對(duì)應(yīng)的弱引用條目淹办。將 __weak 變量的指針保存到弱引用條目的引用指針數(shù)組中完成注冊(cè)眉枕,我們來(lái)看看這個(gè)過(guò)程是怎么樣的

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

上部分,判斷弱引用條目中存放的引用指針數(shù)超過(guò)了 inline_referrers 數(shù)組的容量怜森。如果沒(méi)有超過(guò)的話(有可能容量已滿)速挑,則遍歷 inline_referrers 找到空位置存放 new_referrer。如果 inline_referrers 容量已滿副硅,改用 entry 的 referrers 列表存放引用指針姥宝。首先,將 inline_referrers 中存放的引用指針加到 referrers 中恐疲,更新設(shè)置 num_refs腊满、out_of_line_ness(是否超出了inline_referrers數(shù)組的容量)、mask培己、max_hash_displacement碳蛋。接下來(lái)就進(jìn)入下部分

    assert(entry->out_of_line());

    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
        return grow_refs_and_insert(entry, new_referrer);
    }

如果 entry 的引用指針數(shù)達(dá)到了存放容量的 3/4,那么對(duì) new_referrer 進(jìn)行擴(kuò)容并且插入 new_referrer肃弟。

grow_refs_and_insert

#define TABLE_SIZE(entry) (entry->mask ? entry->mask + 1 : 0)
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);
    size_t new_size = old_size ? old_size * 2 : 8;

    size_t num_refs = entry->num_refs;
    weak_referrer_t *old_refs = entry->referrers;
    entry->mask = new_size - 1;
    
    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);
}

這個(gè)函數(shù)和 weakTable 的擴(kuò)容函數(shù) weak_resize 一樣箩兽,首先通過(guò) old_size 計(jì)算出擴(kuò)容的大小 new_size,然后創(chuàng)建一個(gè) weak_referrer_t 實(shí)例 old_refs 存放 entry 中 referrers 列表的引用指針,然后對(duì) entry 的 referrers 進(jìn)行初始化擴(kuò)容。最后,如果 old_refs 有數(shù)據(jù)(即原 entry 存在的引用指針)许师,將引用指針通過(guò) append_referrer 插入到擴(kuò)容后的 referrers 中,此步驟為遞歸調(diào)用。插入的主要代碼便是 append_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++;
}

上方代碼和 weakTable 的 weak_entry_insert 函數(shù)實(shí)現(xiàn)原理一樣,便不再講述。從上面分析下來(lái)的猛,可見(jiàn) weakTable 的weak_entries 和 entry 的referrers 一樣是一個(gè)可以自動(dòng)擴(kuò)容的數(shù)組,而 entry 的inline_referrers 是一個(gè)不可擴(kuò)容的數(shù)組。

__weak 變量銷毀

__weak 變量銷毀會(huì)調(diào)用 objc_destroyWeak 這個(gè)函數(shù)

void
objc_destroyWeak(id *location)
{
    (void)storeWeak<true/*old*/, false/*new*/, false/*crash*/>
        (location, nil);
}

其中實(shí)現(xiàn)也是通過(guò) storeWeak 函數(shù)將 __weak 變量置為nil正驻,下面只顯示相關(guān)代碼

template <bool HaveOld, bool HaveNew, bool 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) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (HaveNew) {
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

    SideTable::lockTwo<HaveOld, HaveNew>(oldTable, newTable);

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

    // Clean up old value, if any.
    if (HaveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }
    
    SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);

    return (id)newObj;
}

從 objc_destroyWeak 函數(shù)傳入的 HaveOld = true氓栈、HaveNew = false渣磷、CrashIfDeallocating = false,首先將 __weak 變量的內(nèi)存指針指向 oldObj授瘦,將當(dāng)前值變成舊值醋界,對(duì)應(yīng)的從 SideTables 取出對(duì)應(yīng)的 SideTable 為 oldTable,將 newTable 賦值為 nil提完。然后形纺,將 oldTable 和 newTable 加鎖,如果舊值存在并且舊值 __weak 變量?jī)?nèi)存地址中的值和舊值不相等的話徒欣,那么需要重新執(zhí)行第一步驟以保證銷毀工作進(jìn)行逐样。接下來(lái)就是注銷 oldObj 對(duì)應(yīng)的 weak_table 中 __weak 變量的弱引用。最后打肝,解開(kāi) oldTable 和 newTable 的鎖脂新,返回 nil,將 __weak 變量置 nil粗梭。

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;

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

首先争便,通過(guò) weak_entry_for_referent 找到 weak_table 中的弱引用條目 entry,然后通過(guò) remove_referrer 函數(shù)從 entry 的引用指針列表中刪除 __weak變量指針断医。如果 entry 中沒(méi)有引用指針了滞乙,那么便會(huì)執(zhí)行 weak_entry_remove 從弱引用表 weak_table 中刪除該弱引用條目奏纪。

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

如果,entry 的引用指針數(shù)不超過(guò) inline_referrers 的容量斩启,那么遍歷 inline_referrers 找到引用指針的位置并置為nil序调。如果引用指針數(shù)不超過(guò) inline_referrers 的容量,那么便得去 referrers 中找到引用指針置為nil并將 referrers 的長(zhǎng)度減一兔簇。如果找不到便會(huì)調(diào)用 objc_weak_error()

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

    weak_table->num_entries--;

    weak_compact_maybe(weak_table);
}

此函數(shù)將弱引用條目 entry 從 weak_table 中刪除发绢,然后通過(guò) weak_compact_maybe 去檢查是否需要縮小 weak_table 的容量。

weak_compact_maybe

#define TABLE_SIZE(entry) (entry->mask ? entry->mask + 1 : 0)

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

    // Shrink if larger than 1024 buckets and at most 1/16 full.
    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
    }
}

如果達(dá)到縮小容量大小的要求男韧,便通過(guò) weak_resize 函數(shù)調(diào)整容量為原來(lái)的 1/8朴摊。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市此虑,隨后出現(xiàn)的幾起案子甚纲,更是在濱河造成了極大的恐慌,老刑警劉巖朦前,帶你破解...
    沈念sama閱讀 221,820評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件介杆,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡韭寸,警方通過(guò)查閱死者的電腦和手機(jī)春哨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)恩伺,“玉大人赴背,你說(shuō)我怎么就攤上這事【” “怎么了凰荚?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,324評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)褒脯。 經(jīng)常有香客問(wèn)我便瑟,道長(zhǎng),這世上最難降的妖魔是什么番川? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,714評(píng)論 1 297
  • 正文 為了忘掉前任到涂,我火速辦了婚禮,結(jié)果婚禮上颁督,老公的妹妹穿的比我還像新娘践啄。我一直安慰自己,他們只是感情好沉御,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布往核。 她就那樣靜靜地躺著,像睡著了一般嚷节。 火紅的嫁衣襯著肌膚如雪聂儒。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,328評(píng)論 1 310
  • 那天硫痰,我揣著相機(jī)與錄音衩婚,去河邊找鬼。 笑死效斑,一個(gè)胖子當(dāng)著我的面吹牛非春,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播缓屠,決...
    沈念sama閱讀 40,897評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼奇昙,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了敌完?” 一聲冷哼從身側(cè)響起储耐,我...
    開(kāi)封第一講書(shū)人閱讀 39,804評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎滨溉,沒(méi)想到半個(gè)月后什湘,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,345評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡晦攒,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評(píng)論 3 340
  • 正文 我和宋清朗相戀三年闽撤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片脯颜。...
    茶點(diǎn)故事閱讀 40,561評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡哟旗,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出栋操,到底是詐尸還是另有隱情闸餐,我是刑警寧澤,帶...
    沈念sama閱讀 36,238評(píng)論 5 350
  • 正文 年R本政府宣布讼庇,位于F島的核電站绎巨,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蠕啄。R本人自食惡果不足惜场勤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望歼跟。 院中可真熱鬧和媳,春花似錦、人聲如沸哈街。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,417評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)骚秦。三九已至她倘,卻和暖如春璧微,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背硬梁。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,528評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工前硫, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人荧止。 一個(gè)月前我還...
    沈念sama閱讀 48,983評(píng)論 3 376
  • 正文 我出身青樓屹电,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親跃巡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子危号,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評(píng)論 2 359

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