weak的底層原理

在項目中我們經常用到weak指針,其可以保證在指向的對象釋放后,weak指針自動置為nil,以防止崩潰,因為在OC中向nil發送消息是沒有任何處理的。通過__weakproperty weak等形式,都可以將指針修飾為weak類型的。

weak的實現原理其實很簡單,概括來說就是,在內存中有一個名為weak_table_t的哈希表,weak_table_t中存儲著App所有的weak對象及指針。當有對象被weak指針修飾時,會將被修飾的對象及指針添加到weak_table_t表中。當被weak指針的作用域消失時,weak指針會被銷毀,隨后會 從哈希表中查找對應的weak指針,并將指針置為nil

weak引用表

SideTables

weak的實現中很多地方都用到了SideTables函數,此函數內部直接返回了一個哈希表,哈希表名為SideTablesMap。此函數用來保存整個程序所有被weak指針指向的對象,和應用程序是一對一的。每個對象對應其中的一個元素,例如兩個weak指針指向一個對象,則這個對象對應一個SideTable對象,這個對象中保存這兩個weak指針。

按照比較新的runtime 779.1版本,對于SideTables的定義如下。SideTables本質上是一個靜態函數,其內部是通過SideTablesMap實現的。

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

下面定義是一個嵌套模型,其中SideTablesMap是一個objc命名空間下的ExplicitInit類,其內部實現的上面的get函數。StripedMap也是一個模型,傳入的SideTable就是全局的weak表所在的結構體。

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

ExplicitInit

ExplicitInit是一個模板類,傳入的type就是上面定義的StripedMap。在get函數中有reinterpret_cast關鍵字,這個關鍵字類似于強制類型轉換,改變類型但存儲的數據不變。

template <typename Type>
class ExplicitInit {
    alignas(Type) uint8_t _storage[sizeof(Type)];

public:
    template <typename... Ts>
    void init(Ts &&... Args) {
        new (_storage) Type(std::forward<Ts>(Args)...);
    }

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

StripedMap

我們繼續來看StripedMap模板的定義:

template<typename T>
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR //如果是iphone設備Map的數量為8
    enum { StripeCount = 8 };
#else
    enum { StripeCount = 64 };
#endif

    struct PaddedT {
        T value alignas(CacheLineSize); //T value 64字節對齊

    };

    PaddedT array[StripeCount];// 所有PaddedT struct 類型數據被存儲在array數組中(這里的PaddedT 帶入代碼中即 SideTable)。

    static unsigned int indexForPointer(const void *p) {// index的hash算法,該方法以void *作為key 來獲取void *對應在StripedMap 中的位置
        uintptr_t addr = reinterpret_cast<uintptr_t>(p);
        return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
    }
//蘋果為array數組寫了一些公共的存取數據的方法,主要是調用indexForPointer方法,使得外部傳入的對象地址指針直接hash到對應的array節點,這里主要是對&符號重寫,所以我們經常看到&SideTables()[obj]這種調用操作,實際上就是調用這個array[indexForPointer(p)].value從而獲取SideTable
 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]; 
    }

    // Shortcuts for StripedMaps of locks.
    void lockAll() {
        for (unsigned int i = 0; i < StripeCount; i++) {
            array[i].value.lock();
        }
    }

    void unlockAll() {
        for (unsigned int i = 0; i < StripeCount; i++) {
            array[i].value.unlock();
        }
    }

    void forceResetAll() {
        for (unsigned int i = 0; i < StripeCount; i++) {
            array[i].value.forceReset();
        }
    }

    void defineLockOrder() {
        for (unsigned int i = 1; i < StripeCount; i++) {
            lockdebug_lock_precedes_lock(&array[i-1].value, &array[i].value);
        }
    }

    void precedeLock(const void *newlock) {
        // assumes defineLockOrder is also called
        lockdebug_lock_precedes_lock(&array[StripeCount-1].value, newlock);
    }

    void succeedLock(const void *oldlock) {
        // assumes defineLockOrder is also called
        lockdebug_lock_precedes_lock(oldlock, &array[0].value);
    }

    const void *getLock(int i) {
        if (i < StripeCount) return &array[i].value;
        else return nil;
    }
    
#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

SideTable結構體是weak實現的核心,結構體中定義了引用計數表和弱引用表,弱引用使用weak_table字段。下面的slock自旋鎖,兩個表都會使用同一個鎖。

struct SideTable {
    // 自旋鎖,保證線程安全
    spinlock_t slock;
    // 引用計數表,在未開啟isa指針優化,或isa指針存儲滿了才會用
    RefcountMap refcnts;
    // 弱引用表
    weak_table_t weak_table;
};

weak_table_t

weak_table_t是弱引用表,所有的弱引用都會被存儲在這個表中。在下面結構體變量的定義中,weak_entries是一個哈希表的結構,其中key是堆區內存地址,通過key可以獲取weak_entries鏈表中對應的weak_entry_t,也就是指針數組。

struct weak_table_t {
    // 弱引用數組,用來存儲weak_entry_t對象,是一個鏈表結構
    weak_entry_t *weak_entries;
    // 弱引用數組大小,如果到閾值會自動擴容
    size_t    num_entries;
    // 進行哈希運算的mask,大小是num_entries-1
    uintptr_t mask;
    // 最大沖突數,一般不會大于這個數
    uintptr_t max_hash_displacement;
};

weak_entry_t

weak_entry_t結構體內部的定義是一個union聯合體,聯合體中包含兩個結構體,此結構體用來存儲指針地址。在存儲時會判斷,如果同一個對象的weak指針數量少于4,則使用inline_referrers定長數組,否則使用referrers動態長度數組。

有趣的是,因為是union聯合體,當weak指針數量大于4之后,不需要另外開辟空間,在當前空間直接覆蓋inline_referrers的存儲區域,使用referrers哈希表。

#define WEAK_INLINE_COUNT 4

struct weak_entry_t {
    DisguisedPtr<objc_object> referent;
    union {
        struct {
            // 弱引用該對象的,指針地址的哈希數組
            weak_referrer_t *referrers;
            // 是否使用動態長度數組
            uintptr_t        out_of_line_ness : 2;
            // referrers數組中的元素
            uintptr_t        num_refs : PTR_MINUS_2;
            // 參與哈希運算的值,是referrers數組分配的長度,會隨著動態擴容而改變
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        struct {
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };
};

weak_referrer_t

無論是inline_referrers數組還是referrers鏈表,都是一個weak_referrer_t的定義,在這個typedef中定義了一個指向objc_object的指針,isa指針都是被優化為objc_object的,所以這個指針指向這個被優化的isa。也可以簡單理解為,weak_referrer_t本質上存儲著被weak修飾的指針地址。

結合weak_entry_t的定義,可以知道referrers數組存儲結構,就是直接將DisguisedPtr轉換后的整型負數,存儲在referrers數組中。

typedef DisguisedPtr<objc_object *> weak_referrer_t;

DisguisedPtr

DisguisedPtr是一個模板工具類,主要起到將指針和整型相互轉換的作用,目的是為了隱藏指針,起到一個偽裝的作用。隱藏指針的作用在于,一方面是為了安全性,另一方面也防止leaks這些檢測工具的誤判。

template <typename T>
class DisguisedPtr {
    // 存儲地址轉換為整數的結果
    uintptr_t value;
    // 將地址轉為整數,并取反
    static uintptr_t disguise(T* ptr) {
        return -(uintptr_t)ptr;
    }
    // 將轉換為負數的地址,先取反隨后轉換為地址
    static T* undisguise(uintptr_t val) {
        return (T*)-val;
    }

 public:
    // 構造函數
    DisguisedPtr() { }
    // 通過指針,初始化value
    DisguisedPtr(T* ptr) 
        : value(disguise(ptr)) { }
    DisguisedPtr(const DisguisedPtr<T>& ptr) 
        : value(ptr.value) { }
    // 運算符重載,將指針轉換為DisguisedPtr的過程,直接由“&”取地址符來實現
    DisguisedPtr<T>& operator = (T* rhs) {
        value = disguise(rhs);
        return *this;
    }
    DisguisedPtr<T>& operator = (const DisguisedPtr<T>& rhs) {
        value = rhs.value;
        return *this;
    }

    operator T* () const {
        return undisguise(value);
    }
    // 運算符重載,將地址轉為指針
    T* operator -> () const { 
        return undisguise(value);
    }
    T& operator * () const { 
        return *undisguise(value);
    }
    T& operator [] (size_t i) const {
        return undisguise(value)[i];
    }
};

創建weak指針

objc_initWeak

通過__weak等形式創建的指針,編譯器會將其轉換為objc_initWeak函數的調用,并將被指向對象及weak指針傳進去。但需要注意的是,此函數并不是線程并發安全的,所以需要注意多線程的使用。

id objc_initWeak(id *location, id newObj) {
    if (!newObj) {
        *location = nil;
        return nil;
    }

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

在調用函數后會將haveNewnewObj等參數傳入,供storeWeak調用。

storeWeak

無論是創建weak指針還是銷毀weak指針,其內部實現都是通過storeWeak函數實現的。storeWeak函數是一個C++的模板函數,函數會傳入五個參數。其中haveOldhaveNew是互斥的,haveNew則表示新創建的weak指針,haveOld則表示將已有的weak指針置為nil,或將指針重定向。

由于storeWeak函數中的代碼量較大,所以只保留了核心代碼,不重要的代碼都刪掉了,看的也清楚點。如果是通過objc_initWeak調用進來,下面template的三個bool參數定義,分別如下。

  • haveOld = false
  • haveNew = true
  • crashIfDeallocating = true
template <HaveOld haveOld, HaveNew haveNew,
          CrashIfDeallocating crashIfDeallocating>
static id 
storeWeak(id *location, objc_object *newObj)
{
    if (!haveNew) assert(newObj == nil);

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

 retry:
    // 判斷location是否已經指向weak對象,有的話先把舊對象取出來
    if (haveOld) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    // 將新傳入的對象,對應的weak表取出來,如果這個對象之前被weak指針指向過,則返回有值
    if (haveNew) {
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

    // 如果有舊對象,但是不是location所指向的,也就是當前傳入的對象。則表示從retry執行到這里,可能被其他線程改過,所以需要重新執行retry
    if (haveOld  &&  *location != oldObj) {
        goto retry;
    }

    // 有新對象,則判斷類是否初始化,沒有初始化則先初始化,再重新執行retry
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            _class_initialize(_class_getNonMetaClass(cls, (id)newObj));
            previouslyInitializedClass = cls;
            goto retry;
        }
    }

    // 如果location指針指向過其他對象,則執行weak_unregister_no_lock,將weak表和舊對象解綁
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // 將傳入的新對象,以及新對象的指針地址,添加到weak表中
    if (haveNew) {
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating);

        if (newObj  &&  !newObj->isTaggedPointer()) {
            newObj->setWeaklyReferenced_nolock();
        }

        *location = (id)newObj;
    }

    return (id)newObj;
}

storeWeak函數中會進行判斷,如果是haveNew則表示創建一個weak指針,這時會判斷被指向的對象是否第一次傳入,如果是則會創建新的內存空間,如果是第二次被weak指向則取出之前已經創建的weak_table。當釋放weak指針時也是如此,先取出SideTable對象再從weak_table中移除對應的weak指針。

隨后會進入一個判斷,會判斷弱引用對象所屬的類對象是否未執行+initialized方法,如果未執行則調用下面的代碼初始化類對象。以保證在弱引用對象和+initialized方法之間,不會產生死鎖。

最后會進入具體的實現中,這些實現代碼都在objc-weak.mm中。移除weak引用或指針重定向會通過weak_unregister_no_lock函數實現,添加新的weak引用則會通過weak_register_no_lock函數實現。

weak_register_no_lock

weak_register_no_lock函數是添加weak引用的關鍵,在函數中會有是否可以進行weak操作的判斷,如果不符合條件則return或拋出異常。需要注意的是,在NSObject.mm文件中提供了allowsWeakReference方法,可以通過此方法返回是否允許使用weak引用,在下面的代碼中也會向此方法發送消息來做確認。

weak_register_no_lock函數傳入的四個參數如下。

  • weak_table,全局的weak哈希表
  • referent_idweak指針。
  • *referrer_idweak指針地址。
  • crashIfDeallocating,如果weak指向的地址正在釋放中,是否crash
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;
    // 判斷傳入的對象是否為空,以及是否tagged pointer這些邊界判斷
    if (!referent || referent->isTaggedPointer()) return referent_id;

    bool deallocating;
    if (!referent->ISA()->hasCustomRR()) {
        deallocating = referent->rootIsDeallocating();
    }
    else {
        // 有自定義的retain實現,執行自定義方法
        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("error");
        } else {
            return nil;
        }
    }

    // 判斷堆區內存是否有被weak引用過,如果有的話就加入到已有的weak_entry_t中
    weak_entry_t *entry;
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        append_referrer(entry, referrer);
    } 
    else {
        // 如果沒有,則創建一個weak_entry_t,并插入到全局的weak_table哈希表中
        weak_entry_t new_entry(referent, referrer);
        weak_grow_maybe(weak_table);
        weak_entry_insert(weak_table, &new_entry);
    }
    return referent_id;
}

在函數中通過referent獲取到被weak引用的對象,通過referrer獲取到指針對象,隨后會將這兩個變量包裝成weak_entry_t結構體,并調用對應的函數添加到哈希表中。

執行添加操作的有兩個關鍵函數,當對象第一次有weak指針指向時,會調用weak_entry_insertweak_entry_t插入到哈希表中,后面的weak指向都會調用append_referrer函數向表中添加referrer

weak_entry_for_referent

weak_entry_for_referent函數用來查找referentweak指向的堆區地址,也就是objc_object對象的地址。隨后通過hash_pointer函數找到referent所在的index,也就是哈希表的key。在while循環中遍歷weak_entries表,從index開始查找referent所在的位置,一般很快就能找到,并返回。

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;

    // 通過哈希算法獲取到referent的index,也就是所在的下標
    size_t begin = hash_pointer(referent) & weak_table->mask;
    size_t index = begin;
    size_t hash_displacement = 0;
    // 循環時從index所在的下標開始,這樣查找速度會很快
    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];
}

append_referrer

此函數是對weak_entry_t進行插入,向數組后面拼接指向指針的地址。內部會根據哈希表大小,決定用動態數組還是定長數組,并且會根據使用情況,對動態數組進行擴容。并最終插入到weak_entry_t的末尾。

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
    // 如果還未開啟動態長度數組,則進入這里
    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;
            }
        }

        // 如果定長數組存滿了,就創建動態數組,并將定長數組的對象插入進來
        weak_referrer_t *new_referrers = (weak_referrer_t *)
            calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));

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

    ASSERT(entry->out_of_line());

    // 判斷weak_entry_t已經使用的大小,是否超過了已經開辟的3/4,如果超過則進行動態擴容
    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
        return grow_refs_and_insert(entry, new_referrer);
    }

    // 通過哈希算法查找當前的index
    size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    // 通過while循環,找到referrers數組的最新一個為空的位置
    while (entry->referrers[index] != nil) {
        hash_displacement++;
        // 由于擴容是成倍擴容的,所以mask的值一定是,0x111, 0x1111, 0x11111
        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;
    }
    // 將new_referrer插入到新的空位置,并將count加一
    weak_referrer_t &ref = entry->referrers[index];
    ref = new_referrer;
    entry->num_refs++;
}

weak_entry_insert

weak_table_t中定義了weak_entries結構體變量,這是一個數組結構,弱引用對象都保存在這里。當插入一個weak_entry_t時會遍歷數組,并找到合適的位置將其插入。

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

指針重定向

有時候會把weak指針指向一個新的對象,這時候會涉及到指針重定向。新建weak指針會傳入newObj并調用對應的函數,weak指針作用域銷毀會傳入oldObj并調用對應的函數,而指針重定向則這兩個參數都有值,haveNewhaveOld也都是有值的。

指針重定向會在一次storeWeak函數調用中處理,并按順序先調用weak_unregister_no_lock函數,先將weak指針從哈希表中移除,再調用weak_register_no_lock函數添加到新的SideTable中。

dealloc

rootDealloc

當對象釋放時,會調用dealloc方法,并調用到rootDealloc來實現釋放邏輯。在釋放時會判斷,如果沒有弱引用等邏輯,可以直接調用free函數釋放,否則調用object_dispose函數處理釋放邏輯。

inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;
    // 如果符合下面條件,則直接調用free函數釋放內存
    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);
    }
}

objc_destructInstance

object_dispose函數中調用的objc_destructInstance函數,在objc_destructInstance函數中,執行了一些釋放和收尾的工作。而weak的釋放操作,就在clearDeallocating中完成的。

void *objc_destructInstance(id obj) 
{
    if (obj) {
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}

clearDeallocating

clearDeallocating函數中,會判斷是否開啟指針優化,如果未開啟則執行sidetable的釋放邏輯。如果開啟了指針優化,并且有weak指針,或引用計數的邏輯,則執行clearDeallocating_slow函數。

inline void 
objc_object::clearDeallocating()
{
    if (slowpath(!isa.nonpointer)) {
        sidetable_clearDeallocating();
    }
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
        clearDeallocating_slow();
    }

    assert(!sidetable_present());
}

clearDeallocating_slow函數內部,實際上通過weak_clear_no_lock函數對weak指針釋放邏輯進行的實現。

weak_clear_no_lock

weak最核心的功能,即對象釋放時,將weak指針指向nil。這個邏輯就在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,從weak_table中找到weak_entry_t結構體
    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    if (entry == nil) {
        return;
    }

    weak_referrer_t *referrers;
    size_t count;

    // 判斷用的是定長數組,還是動態數組,并將數組賦值給referrers指針
    if (entry->out_of_line()) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }

    // 遍歷weak數組,并將指向weak堆區地址的指針,都置為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哈希表中,將weak_entry_t移除
    weak_entry_remove(weak_table, entry);
}

釋放優化

objc_destroyWeak

weak作為一個局部變量出現時,編譯器會對weak進行優化。創建對象后,會通過objc_initWeak的方式將weak指針添加到哈希表中。在作用域結束時,會通過objc_destroyWeakweak直接釋放掉。

weak指針作用域消失時,系統會調用objc_destroyWeak函數來處理,并在函數內部調用storeWeak函數。隨后storeWeak函數中會傳入haveOld,表示是執行移除weak的操作。

void objc_destroyWeak(id *location)
{
    (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
        (location, nil);
}

weak_unregister_no_lock

weak_unregister_no_lock函數是在storeWeak函數中調用的,當weak指針第一次指向或重新指向時,都會調用storeWeak函數。如果重新指向,會調用weak_unregister_no_lock先將之前的移除掉。

函數會通過referent獲取到被weak引用的對象,通過referrer獲取到指針對象。隨后會通過weak_entry_for_referent函數查找被指向的對象是否有weak_entry_t,如果找到則進入if語句中。

if語句中會調用remove_referrer函數,將被weak指針從weak_entry_t中移除,并將weak指針置nil,這步就是最關鍵的一步了。隨后會判斷,如果被指向的對象,所有weak指針都沒有了,則將對象從weak_table的哈希表中移除。

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;

    // 從weak_table中獲取weak_entry_t對象,如果有的話則進入if語句
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        // 從weak_entry_t中刪除referrer引用
        remove_referrer(entry, referrer);
        // 判斷刪除后,weak_entry_t是否還有弱引用指針指向這塊內存地址
        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;
                }
            }
        }
        // 如果為空,則將weak_entry_t從全局weak_table哈希表中刪除
        if (empty) {
            weak_entry_remove(weak_table, entry);
        }
    }
}

remove_referrer

這是weak實現的關鍵函數,和向weak_entry_t中添加一樣,只是這個過程是逆向的。會先遍歷inline_referrers數組中有沒有指針對象,如果有則將指針置為nil,否則就查找referrers數組。

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

    size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
    size_t index = begin;
    while (entry->referrers[index] != old_referrer) {
        index = (index+1) & entry->mask;
        if (index == begin) bad_weak_table(entry);
    }
    entry->referrers[index] = nil;
    entry->num_refs--;
}
weak底層原理
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,030評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,310評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,951評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,796評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,566評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,055評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,142評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,303評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,799評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,683評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,899評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,409評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,135評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,520評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,757評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,528評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,844評論 2 372

推薦閱讀更多精彩內容