Object Runtime -- Weak

半紙淵發表於2019-03-28

零:導讀

第一部分:借用 Xcode 找到與 weak 有關的所有內容,並進行整理和歸納,推匯出核心的資料結構和方法集。

第二部分:羅列和分析 weak 相關的重要資料結構,為後面的方法集分析做準備。

第三部分:根據生命週期,重新羅列和分析 weak 的相關的方法集。

第四部分:分析第一部分留下的核心方法的實現,並做一個簡單的問答式總結。

第一部分:Weak 的組成結構

找到 Weak 相關的內容

下載 Objc4 原始碼 :obj4,並用 Xcode 開啟。[ 本文使用的是 objc4-750.1 ]

Xcode->Navigator->Show the find Navigator-> weak

直接搜尋 weak,找到相關的可用內容

runtime.h

// 定義與宣告等內容
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_ASSIGN = 0,    
    /**< */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
    OBJC_ASSOCIATION_RETAIN = 01401,  
    OBJC_ASSOCIATION_COPY = 01403
};
複製程式碼
// 方法集
OBJC_EXPORT const uint8_t * _Nullable
class_getWeakIvarLayout(Class _Nullable cls) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

OBJC_EXPORT void
class_setWeakIvarLayout(Class _Nullable cls, const uint8_t * _Nullable layout)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

OBJC_EXPORT id _Nullable
objc_loadWeak(id _Nullable * _Nonnull location) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0);

OBJC_EXPORT id _Nullable
objc_storeWeak(id _Nullable * _Nonnull location, id _Nullable obj) 
    OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0);
複製程式碼

objc-internal.h

// 定義與宣告等內容
typedef enum {
    objc_ivar_memoryUnknown,     // unknown / unknown
    objc_ivar_memoryStrong,      // direct access / objc_storeStrong
    objc_ivar_memoryWeak,        // objc_loadWeak[Retained] / objc_storeWeak
    objc_ivar_memoryUnretained   // direct access / direct access
} objc_ivar_memory_management_t;
複製程式碼
// 方法集
OBJC_EXPORT id _Nullable
objc_loadWeakRetained(id _Nullable * _Nonnull location)
    OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0);

OBJC_EXPORT id _Nullable 
objc_initWeak(id _Nullable * _Nonnull location, id _Nullable val)
    OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0);

OBJC_EXPORT id _Nullable
objc_storeWeakOrNil(id _Nullable * _Nonnull location, id _Nullable obj)
    OBJC_AVAILABLE(10.11, 9.0, 9.0, 1.0, 2.0);

OBJC_EXPORT id _Nullable
objc_initWeakOrNil(id _Nullable * _Nonnull location, id _Nullable val) 
    OBJC_AVAILABLE(10.11, 9.0, 9.0, 1.0, 2.0);

OBJC_EXPORT void
objc_destroyWeak(id _Nullable * _Nonnull location) 
    OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0);

OBJC_EXPORT void 
objc_copyWeak(id _Nullable * _Nonnull to, id _Nullable * _Nonnull from)
    OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0);

OBJC_EXPORT void 
objc_moveWeak(id _Nullable * _Nonnull to, id _Nullable * _Nonnull from) 
    OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0);
複製程式碼

objc-object.h

// 方法集
#if SUPPORT_NONPOINTER_ISA
inline bool
objc_object::isWeaklyReferenced()
{
    assert(!isTaggedPointer());
    if (isa.nonpointer) return isa.weakly_referenced;
    else return sidetable_isWeaklyReferenced();
}


inline void
objc_object::setWeaklyReferenced_nolock()
{
 retry:
    isa_t oldisa = LoadExclusive(&isa.bits);
    isa_t newisa = oldisa;
    if (slowpath(!newisa.nonpointer)) {
        ClearExclusive(&isa.bits);
        sidetable_setWeaklyReferenced_nolock();
        return;
    }
    if (newisa.weakly_referenced) {
        ClearExclusive(&isa.bits);
        return;
    }
    newisa.weakly_referenced = true;
    if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
}

#else 

inline bool
objc_object::isWeaklyReferenced()
{
    assert(!isTaggedPointer());

    return sidetable_isWeaklyReferenced();
}


inline void 
objc_object::setWeaklyReferenced_nolock()
{
    assert(!isTaggedPointer());

    sidetable_setWeaklyReferenced_nolock();
}

#endif
複製程式碼

objc-private.h

// 定義與宣告等內容

/* ISA_BITFIELD in isa.h */
// xxx 是表示 arm64 / arm32 / x86_64 不同平臺的值不一樣
#   define ISA_BITFIELD                                                      \
      uintptr_t nonpointer        : xxx;                                       \
      uintptr_t has_assoc         : xxx;                                       \
      uintptr_t has_cxx_dtor      : xxx;                                       \
      uintptr_t shiftcls          : xxx; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
      uintptr_t magic             : xxx;                                       \
      uintptr_t weakly_referenced : xxx;                                       \
      uintptr_t deallocating      : xxx;                                       \
      uintptr_t has_sidetable_rc  : xxx;                                       \
      uintptr_t extra_rc          : xxx

/* isa */
union isa_t {
    // ...
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

/* objc_object */
struct objc_object {
private:
    isa_t isa;

    // ...
public:
    // ...
    // object may be weakly referenced?
    bool isWeaklyReferenced();
    void setWeaklyReferenced_nolock();
    // ...

private:
    // ...
#if SUPPORT_NONPOINTER_ISA
    // ...
 
    // Side table retain count overflow for nonpointer isa
    void sidetable_lock();
    void sidetable_unlock();

    void sidetable_moveExtraRC_nolock(size_t extra_rc, bool isDeallocating, bool weaklyReferenced);
    bool sidetable_addExtraRC_nolock(size_t delta_rc);
    size_t sidetable_subExtraRC_nolock(size_t delta_rc);
    size_t sidetable_getExtraRC_nolock();
#endif

    // Side-table-only retain count
    bool sidetable_isDeallocating();
    void sidetable_clearDeallocating();

    bool sidetable_isWeaklyReferenced(); // weak
    void sidetable_setWeaklyReferenced_nolock(); // weak

    id sidetable_retain();
    id sidetable_retain_slow(SideTable& table);

    uintptr_t sidetable_release(bool performDealloc = true);
    uintptr_t sidetable_release_slow(SideTable& table, bool performDealloc = true);

    bool sidetable_tryRetain();

    uintptr_t sidetable_retainCount();
#if DEBUG
    bool sidetable_present();
#endif
};

// layout.h
typedef struct {
    uint8_t *bits;
    size_t bitCount;
    size_t bitsAllocated;
    bool weak;
} layout_bitmap;
複製程式碼
extern SEL SEL_retainWeakReference;
extern SEL SEL_allowsWeakReference;

extern bool noMissingWeakSuperclasses(void);
複製程式碼

objc-runtime-new.h

#if !__LP64__

// for class_rw_t->flags or class_t->bits
// class or superclass has default retain/release/autorelease/retainCount/
//   _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference
# define RW_HAS_DEFAULT_RR     (1<<14)

// class or superclass has default retain/release/autorelease/retainCount/
//   _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference
# define FAST_HAS_DEFAULT_RR     (1UL<<2)

#else 

// class or superclass has default retain/release/autorelease/retainCount/
//   _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference
# define FAST_HAS_DEFAULT_RR     (1UL<<49)

#endif

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    // ...
    const class_ro_t *ro;
    // ...
};

// for class_ro_t->flags
// class is not ARC but has ARC-style weak ivar layout 
#define RO_HAS_WEAK_WITHOUT_ARC (1<<9)

struct class_ro_t {
    uint32_t flags;
    // ...
    const uint8_t * weakIvarLayout;
	    // ...
};
複製程式碼

objc-weak.h

// 定義與宣告等內容

// DisguisedPtr 在 objc-private.h 中宣告
typedef DisguisedPtr<objc_object *> weak_referrer_t;

#if __LP64__
# define PTR_MINUS_2 62
#else
# define PTR_MINUS_2 30
#endif

#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 {
            // out_of_line_ness field is low bits of inline_referrers[1]
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };

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

    weak_entry_t& operator=(const weak_entry_t& other) {
        memcpy(this, &other, sizeof(other));
        return *this;
    }

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

struct weak_table_t {
    weak_entry_t *weak_entries;
    size_t    num_entries;
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};
複製程式碼
// 方法集
/// Adds an (object, weak pointer) pair to the weak table.
id weak_register_no_lock(weak_table_t *weak_table, id referent, 
                         id *referrer, bool crashIfDeallocating);

/// Removes an (object, weak pointer) pair from the weak table.
void weak_unregister_no_lock(weak_table_t *weak_table, id referent, id *referrer);

#if DEBUG
/// Returns true if an object is weakly referenced somewhere.
bool weak_is_registered_no_lock(weak_table_t *weak_table, id referent);
#endif

/// Called on object destruction. Sets all remaining weak pointers to nil.
void weak_clear_no_lock(weak_table_t *weak_table, id referent);
複製程式碼

NSObject.mm

// 定義與宣告等內容

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;
    // ...    
};
複製程式碼
template <HaveOld haveOld, HaveNew haveNew,
          CrashIfDeallocating crashIfDeallocating>
static id storeWeak(id *location, objc_object *newObj);
複製程式碼

objc-weak.mm

// 方法集

// Privates
static void grow_refs_and_insert(weak_entry_t *entry, objc_object **new_referrer);

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer);

static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer);

static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry);

static void weak_resize(weak_table_t *weak_table, size_t new_size);

static void weak_grow_maybe(weak_table_t *weak_table);

static void weak_compact_maybe(weak_table_t *weak_table);

static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry);

static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent);

// Publics
void weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,  id *referrer_id);

id weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                         id *referrer_id, bool crashIfDeallocating);

#if DEBUG
bool weak_is_registered_no_lock(weak_table_t *weak_table, id referent_id);
#endif

void weak_clear_no_lock(weak_table_t *weak_table, id referent_id);
複製程式碼

以 初始化 -> 銷燬 的方式檢視原始碼,重新排列所有內容 [ 脫離檔案 ]

Step Start: init

objc_initWeak(...)
objc_initWeakOrNil(...)
	    --> storeWeak(...) ?
        --> weak_unregister_no_lock(...) ?
            --> weak_entry_for_referent(...)
            --> remove_referrer(...)
            --> weak_entry_remove(...)

        --> weak_register_no_lock(...) ?
            --> SEL_allowsWeakReference
            --> SEL_allowsWeakReference
            --> weak_entry_for_referent(...)
            --> append_referrer(...)
            --> weak_grow_maybe(...)
            --> weak_entry_insert(...)

        --> objc_object::setWeaklyReferenced_nolock(...)
            --> objc_object::sidetable_setWeaklyReferenced_nolock(...)
            --> weakly_referenced // ISA_BITFIELD
複製程式碼

Step End: destroy

objc_destroyWeak(...)
    --> storeWeak(...) ?
複製程式碼

Step Load Or Store: objc_xxxWeak

與 init Or destroy 相似的,莫過於 load Or store 了。

load

objc_loadWeak(...)
    --> objc_loadWeakRetained(...)
        --> SEL_retainWeakReference
複製程式碼

store

objc_storeWeak(...)
    --> storeWeak(...) ?
複製程式碼
objc_storeWeakOrNil(...)
    --> storeWeak(...) ?
複製程式碼

Step Others: others

copy

objc_copyWeak(...)
    --> objc_loadWeakRetained(...)
    --> objc_initWeak(...)
複製程式碼

move

objc_moveWeak(...)
    --> objc_copyWeak(...)
    --> objc_destroyWeak(...)
複製程式碼

ivar

// class_ro_t->weakIvarLayout

const uint8_t *
class_getWeakIvarLayout(Class cls)
{
    if (cls) return cls->data()->ro->weakIvarLayout;
    else return nil;
}

void
class_setWeakIvarLayout(Class cls, const uint8_t *layout)
{
    // ...

    class_ro_t *ro_w = make_ro_writeable(cls->data());

    try_free(ro_w->weakIvarLayout);
    ro_w->weakIvarLayout = ustrdupMaybeNil(layout);
}
複製程式碼

結:很明顯,storeWeak 方法就是要研究的核心方法。

storeWeak 方法的實現

您可能還不能看懂它具體在幹嘛,通過二、三部分的分析,就可以看清這個方法的全貌了。

// NSObject.mm

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

    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));
       
            previouslyInitializedClass = cls;

            goto retry;
        }
    }

    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    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;
    }
    
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    return (id)newObj;
}
複製程式碼
  • 裡面有一個很重要的資料結構 SideTable
  • 兩個重要的方法:weak_register_no_lockweak_unregister_no_lock
    • 它們的內部有兩個重要的資料結構 weak_table_tweak_entry_t

第二部分:資料結構

StripMap -> SideTable —> weak_table_t —> weak_entry_t —> DisguisedPtr

DisguisedPtr

/* [封裝 nil ptr] 
 * 這個類就是為了讓 nil 指標像 non-nil 指標那樣『正常』執行它的操作,而不會讓程式崩掉。 
 */

// DisguisedPtr <T> 的作用類似指標型別 T*,除了
// 將儲存值隱藏起來,使其不受 “leaks” 之類工具的影響。 
// nil 被偽裝成它自己,這樣零填充的記憶體就像預期的那樣工作,
// 表示 0x80..00 也偽裝成它自己,但我們不在乎。
// 注意 weak_entry_t 知道這種編碼。
template <typename T>
class DisguisedPtr { // Disguise n. 偽裝、假裝、隱瞞 的意思
    uintptr_t value;

    static uintptr_t disguise(T* ptr) {
        return -(uintptr_t)ptr;
    }

    static T* undisguise(uintptr_t val) {
        return (T*)-val;
    }

 public:
    DisguisedPtr() { }
    // :xxx, 初始化列表,顯式初始化成員(編譯器強制)
    // 等價於 this->value = disguise(ptr);
    DisguisedPtr(T* ptr) : value(disguise(ptr)) { } 
    DisguisedPtr(const DisguisedPtr<T>& ptr) : value(ptr.value) { }

    // 運算子過載
    // this 就是一個指向自己的指標
    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];
    }

    // pointer arithmetic operators omitted 
    // because we don't currently use them anywhere
};
複製程式碼

weak_entry_t

struct weak_entry_t {
    // 儲存 weak 物件的指標
    DisguisedPtr<objc_object> referent;
    union {
        struct {
            // typedef DisguisedPtr<objc_object *> weak_referrer_t;
            // 儲存 weak 物件指標的指標
            weak_referrer_t *referrers;
            // C 語言的 位域(bitfield) 技術,可以節省空間
            // out_of_line_ness + num_refs 剛好是一個 uintptr_t 的大小
       
            // : 2 指變數 out_of_line_ness 只佔 2 位(bit)
            uintptr_t        out_of_line_ness : 2;  // 標記是否越界 (即是否還能儲存新的值)
            // 一共有多少個 弱引用
            uintptr_t        num_refs : PTR_MINUS_2; // PTR_MINUS_2 = __LP64__ ? 62 : 30;
            uintptr_t        mask; // mask 就是陣列的最大下標值,它主要是用來獲取 index 的輔助值 (指標定址),index 其實就是 Hash Key
            uintptr_t        max_hash_displacement; // 記錄已經放置了多少個 Hash Key 
        };
        struct {
            // 儲存所有的 weak 物件指標
            // out_of_line_ness field is low bits of inline_referrers[1]
            // WEAK_INLINE_COUNT = 4
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };

    bool out_of_line() {
        // REFERRERS_OUT_OF_LINE = 2
        return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
    }
    // 運算子過載
    weak_entry_t& operator=(const weak_entry_t& other) {
        // this 指向 other 的記憶體
        memcpy(this, &other, sizeof(other)); // 記憶體拷貝
        return *this;
    }
    // 構造器
    weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
        : referent(newReferent)
    {
        // 儲存新的物件地址的地址,*newReferrer 就是 isa 的記憶體地址了
        inline_referrers[0] = newReferrer;
        // 清空 out_of_line_ness,num_refs,mask 的內容
        for (int i = 1; i < WEAK_INLINE_COUNT; i++) { // WEAK_INLINE_COUNT = 4
            inline_referrers[i] = nil; // nil 就是 0
        }
    }
};
複製程式碼

weak_table_t

/**
 * The global weak references table. Stores object ids as keys,
 * and weak_entry_t structs as their values.
 */
struct weak_table_t {
    weak_entry_t *weak_entries; // 就是一個 weak_entry_t 陣列
    size_t    num_entries; // weak_entry_t 的數量
    uintptr_t mask; // mask 就是陣列的最大下標值,它主要是用來獲取 index 的輔助值 (指標定址),index 其實就是 Hash Key
    uintptr_t max_hash_displacement; // 記錄已經放置了多少個 Hash Key 
};
複製程式碼

SideTable

// Template parameters.
enum HaveOld { DontHaveOld = false, DoHaveOld = true };
enum HaveNew { DontHaveNew = false, DoHaveNew = true };

struct SideTable {
    // 內部封裝的是 os_unfair_lock ,自旋鎖
    spinlock_t slock; 
    // typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap;
    // 物件引用計數的雜湊表
    RefcountMap refcnts;
    // 弱引用表資料結構
    weak_table_t weak_table;

    SideTable() {
        // 初始化:weak_table 記憶體清零,最後效果上 weak_table = nil;
        memset(&weak_table, 0, sizeof(weak_table));
    }

    ~SideTable() {
        _objc_fatal("Do not delete SideTable.");
    }

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

    // Address-ordered lock discipline for a pair of side tables.

    template<HaveOld, HaveNew>
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template<HaveOld, HaveNew>
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
複製程式碼

補充:

DenseMap 就是一個雜湊表。(key-value)

它使用的是平方探測法生成雜湊值(key),還有一種就是線性探測(開放地址法)。

DenseMap

os_unfair_lock: 替代 OSSpinLock

Appe Synchronization

StripMap

/// 它的功能就是把自旋鎖的鎖操作從類中分離出來,而且類中必須要有一個自旋鎖屬性
/// StripMap 內部的實質是一個開放定址法生成雜湊鍵值的雜湊表 (雖然是寫著的 array ,但是是一個雜湊表)

enum { CacheLineSize = 64 };

// StripedMap<T> 是一個void* -> T 的對映,大小適當
// 用於快取友好的鎖分離。
// 例如,這可以用作 StripedMap<spinlock_t>
// 或者是 StripedMap<SomeStruct>,其中 SomeStruct 儲存一個自旋鎖。
template<typename T>
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
    enum { StripeCount = 8 };
#else
    enum { StripeCount = 64 };
#endif

    struct PaddedT {
        T value alignas(CacheLineSize);
    };
    // Hash 表資料 
    PaddedT array[StripeCount];
    
    // Hash 鍵值生成函式
    // 根據物件的記憶體地址計算,陣列中的具體下標值
    static unsigned int indexForPointer(const void *p) {
        uintptr_t addr = reinterpret_cast<uintptr_t>(p);
        return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
    }

 public:
    // 操作符過載

    // 即可以使用 StripMap<xxx>[objcPtr] 訪問 
    T& operator[] (const void *p) { 
        return array[indexForPointer(p)].value; 
    }
    const T& operator[] (const void *p) const { 
        return const_cast<StripedMap<T>>(this)[p]; 
    }

    // 各種 for ,就是遍歷操作元素的所有鎖方法

    // 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 方法集的實現,再檢視 objc_object 使用 SideTable 的方法集。

SideTable Methods

本質上都是使用 class mutex_tt 封裝的 os_unfair_lock 自旋鎖。不難理解 ,就不細說了~~

template<>
void SideTable::lockTwo<DoHaveOld, DoHaveNew>
    (SideTable *lock1, SideTable *lock2)
{
    spinlock_t::lockTwo(&lock1->slock, &lock2->slock);
}

template<>
void SideTable::lockTwo<DoHaveOld, DontHaveNew>
    (SideTable *lock1, SideTable *)
{
    lock1->lock();
}

template<>
void SideTable::lockTwo<DontHaveOld, DoHaveNew>
    (SideTable *, SideTable *lock2)
{
    lock2->lock();
}

template<>
void SideTable::unlockTwo<DoHaveOld, DoHaveNew>
    (SideTable *lock1, SideTable *lock2)
{
    spinlock_t::unlockTwo(&lock1->slock, &lock2->slock);
}

template<>
void SideTable::unlockTwo<DoHaveOld, DontHaveNew>
    (SideTable *lock1, SideTable *)
{
    lock1->unlock();
}

template<>
void SideTable::unlockTwo<DontHaveOld, DoHaveNew>
    (SideTable *, SideTable *lock2)
{
    lock2->unlock();
}
複製程式碼

objc_object::sideTable Methods

? ?? SideTable 裡面的 weak_table_t ? 才是真正的 weak 表,SideTable 是包含整個 MRC 記憶體管理的表結構。

// 我們不能使用 c++ 靜態初始化器來初始化 SideTables,因為 libc 會在 c++ 初始化器執行之前呼叫我們。我們也不想要這個結構的全域性指標,因為額外的間接。只能用困難的方法去做。
alignas(StripedMap<SideTable>) static uint8_t 
    SideTableBuf[sizeof(StripedMap<SideTable>)];

// Map: NSObject * (key) -- SideTable& (value)
static StripedMap<SideTable>& SideTables() {
    return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}

// CPU: Binary MSB LSB; 
// MSB = Most significant bit; 最高有效位
// LSB = Least significant bit; 最低有效位
// MSB-ward 最高有效位監控

// 因為是使用位域的技術,所以標記位的位置當然很重要!!!
// The order of these bits is important.
#define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0)
#define SIDE_TABLE_DEALLOCATING      (1UL<<1) // MSB-ward of weak bit,監控 weak 的位
#define SIDE_TABLE_RC_ONE            (1UL<<2) // MSB-ward of deallocating bit,監控正在銷燬的位
// 這個位其實就是標記當前的引用計數值指標是否使用了位域技術的標記位。
#define SIDE_TABLE_RC_PINNED         (1UL<<(WORD_BITS-1)) // __LP64__ ? 64 : 32 ; 鎖定位

#define SIDE_TABLE_RC_SHIFT 2
#define SIDE_TABLE_FLAG_MASK (SIDE_TABLE_RC_ONE-1) // 就是 3,也就是引用計數值真實值的起始位
複製程式碼

首先我們知道,物件都是從 retainrelease 的,retain 表示擁有,release 表示不擁有。

Reference counting

uintptr_t
objc_object::sidetable_retainCount()
{
    // 從全域性 SideTable 中獲取當前物件的 SideTable
    SideTable& table = SideTables()[this];
    // 它的值是從 1 開始的, 因為 0 是要直接拿去銷燬的~ 
    size_t refcnt_result = 1;
    
    // 自旋鎖 -- 鎖
    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    // end() 和 second 是 C++ 的東西
    // end() 指向 map (樹) 的最後一節點,
    // second 是 pair 裡面的 value, 就是要找的 SideTable 下對應的 引用計數值
    if (it != table.refcnts.end()) {
   
        // #define SIDE_TABLE_RC_SHIFT 2
        // this is valid for SIDE_TABLE_RC_PINNED too
   
        // 因為使用了位域技術,引用計數值前面的二進位制位是:
        // SIDE_TABLE_WEAKLY_REFERENCED( 0 位),SIDE_TABLE_DEALLOCATING( 1 位)
        // SIDE_TABLE_RC_ONE( 2 位),剛好要移動 2 個位,進而取出正確的引用計數值
        
        refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
    }
    // 自旋鎖 -- 解鎖
    table.unlock();
    return refcnt_result;
}
複製程式碼

注意:以下的幾個方法都是在平臺可以使用位域的情況下才會有效的。SUPPORT_NONPOINTER_ISA = YES 時生效。

// 將整個引用計數移到 SideTable,不管是正在釋放的還是弱引用的都移過去。
void 
objc_object::sidetable_moveExtraRC_nolock(size_t extra_rc, 
                                          bool isDeallocating, 
                                          bool weaklyReferenced)
{
    assert(!isa.nonpointer);        // should already be changed to raw pointer
    // 從全域性 SideTable 中獲取當前物件的 SideTable
    SideTable& table = SideTables()[this];

    // 獲取 引用計數值 (特殊儲存的值,就是使用了位域技術處理的值)
    size_t& refcntStorage = table.refcnts[this];
    size_t oldRefcnt = refcntStorage;
    // not deallocating - that was in the isa
    assert((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);  
    assert((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);  

    // 是否使用 位域 技術
    uintptr_t carry;
    // addc (address carry) 基本猜測是 記憶體地址攜帶 功能,即 位域處理,
    // 這個方法就是用來儲存引用計數值的,不過會按照是否使用位域技術進行儲存罷了。
    size_t refcnt = addc(oldRefcnt, extra_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
    // 鎖定位,使用了位域技術
    // SIDE_TABLE_RC_PINNED 是最後一位的前一位,這裡的賦值就是初始化 引用計數值 的意思
    if (carry) refcnt = SIDE_TABLE_RC_PINNED; 
    if (isDeallocating) refcnt |= SIDE_TABLE_DEALLOCATING; // 置位 析構標記位
    if (weaklyReferenced) refcnt |= SIDE_TABLE_WEAKLY_REFERENCED; // 置位 弱引用標記位

    // 儲存新的 引用計數值,size_t& 具備指標改值的功能哦!
    refcntStorage = refcnt;
}
複製程式碼
// 將一些引用計數從 isa 位域移到 SideTable。如果物件現在被鎖定 (SIDE_TABLE_RC_PINNED == 1),則返回 true。
// 首先 isa 也使用了位域技術,不過它被稱為 Tagged Pointer 標記指標罷了。isa 裡面也儲存了很多資訊。
bool 
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
    assert(isa.nonpointer);
    // 從全域性 SideTable 中獲取當前物件的 SideTable
    SideTable& table = SideTables()[this];

    // 獲取 引用計數值 (特殊儲存的值,就是使用了位域技術處理的值)
    size_t& refcntStorage = table.refcnts[this];
    size_t oldRefcnt = refcntStorage;
    // isa-side bits should not be set here
    assert((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
    assert((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);

    if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;

    // 是否使用 位域 技術
    uintptr_t carry;
    // addc (address carry) 基本猜測是 記憶體地址攜帶 功能,即 位域處理,
    // 這個方法就是用來儲存引用計數值的,不過會按照是否使用位域技術進行儲存罷了。
    size_t newRefcnt = 
        addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
    if (carry) {
        // 標記為位域技術處理後的引用計數值,並把真實的引用計數值儲存吉祥指標中
        refcntStorage =
            SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
        return true;
    }
    else {
        // 直接賦值,值本身就是引用計數值的真實值
        refcntStorage = newRefcnt;
        return false;
    }
}
複製程式碼
// 將引用計數值從 SideTable 移動到 isa 位域中。返回減去的實際引用計數值,該值可能小於請求的值。
size_t 
objc_object::sidetable_subExtraRC_nolock(size_t delta_rc)
{
    assert(isa.nonpointer);
    // 從全域性 SideTable 中獲取當前物件的 SideTable
    SideTable& table = SideTables()[this];
    // 沒有引用計數值或者引用計數值為 0 (已經銷燬了),直接返回 0 。
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()  ||  it->second == 0) {
        // Side table retain count is zero. Can't borrow.
        return 0;
    }

    size_t oldRefcnt = it->second;

    // isa-side bits should not be set here
    assert((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
    assert((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);
    
    // delta_rc 使用了位域,最高位是 1 
    size_t newRefcnt = oldRefcnt - (delta_rc << SIDE_TABLE_RC_SHIFT);
    assert(oldRefcnt > newRefcnt);  // shouldn't underflow
    it->second = newRefcnt;
    return delta_rc;
}
複製程式碼
// 返回引用計數值
size_t 
objc_object::sidetable_getExtraRC_nolock()
{
    assert(isa.nonpointer);
    SideTable& table = SideTables()[this];
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()) return 0;
    else return it->second >> SIDE_TABLE_RC_SHIFT;
}
複製程式碼

Retain

id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.nonpointer);
#endif
    // 從全域性 SideTable 中獲取當前物件的 SideTable
    SideTable& table = SideTables()[this];
    
    // 自旋鎖 - 鎖
    table.lock();
    // 獲取 引用計數值 (特殊儲存的值,可能使用了位域技術處理的值)
    size_t& refcntStorage = table.refcnts[this];
    // 如果是沒有使用位域技術處理的引用計數值,直接加 4,
    // 對應於指標就是剛好是第三位,肯定是配合位域技術而做的特殊處理
    if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
        refcntStorage += SIDE_TABLE_RC_ONE;
    }
    // 自旋鎖 - 解鎖
    table.unlock();

    return (id)this;
}
複製程式碼
bool
objc_object::sidetable_tryRetain()
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.nonpointer);
#endif
    // 從全域性 SideTable 中獲取當前物件的 SideTable
    SideTable& table = SideTables()[this];

    // NO SPINLOCK HERE
    // _objc_rootTryRetain() is called exclusively by _objc_loadWeak(), 
    // which already acquired the lock on our behalf.

    // fixme can't do this efficiently with os_lock_handoff_s
    // if (table.slock == 0) {
    //     _objc_fatal("Do not call -_tryRetain.");
    // }

    bool result = true;
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()) {
        // 指引用計數值是 0 了,直接為 4 ,就是引用計數值重新變成『 1 』
        table.refcnts[this] = SIDE_TABLE_RC_ONE;
    } else if (it->second & SIDE_TABLE_DEALLOCATING) {
        result = false; // 如果正在銷燬,直接返回,表示不能被持有
    } else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
        // 如果是沒有使用位域技術處理的引用計數值,直接減 4,
        // 對應於指標就是剛好是第三位,肯定是配合位域技術而做的特殊處理
        it->second += SIDE_TABLE_RC_ONE; 
    }
    
    return result;
}
複製程式碼

Release

// rdar://20206767
// return uintptr_t instead of bool so that the various raw-isa 
// -release paths all return zero in eax
uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.nonpointer);
#endif
    // 從全域性 SideTable 中獲取當前物件的 SideTable
    SideTable& table = SideTables()[this];

    bool do_dealloc = false;

    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()) {
        // 引用計數值為 0 , 標記為要銷燬,同時標記引用計數值的正在銷燬位
        do_dealloc = true;
        // 直接賦值,就是把除正在銷燬的其它 所有位置 0 了 。 
        table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
    } else if (it->second < SIDE_TABLE_DEALLOCATING) {
        // SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
        // 引用計數值為 0 , 標記為要銷燬,同時標記引用計數值的正在銷燬位
        // 這裡的置位,就是單純的置位,沒有破壞其它的標記位的值
        do_dealloc = true;
        it->second |= SIDE_TABLE_DEALLOCATING;
    } else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
        // 引用計數值減 『 1 』
        it->second -= SIDE_TABLE_RC_ONE;
    }
    table.unlock();
    if (do_dealloc  &&  performDealloc) {
        // 向物件傳送 dealloc 訊息
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
    }
    return do_dealloc;
}
複製程式碼
// 檢視標記位,是否被標記為正在銷燬
bool 
objc_object::sidetable_isDeallocating()
{
    // 從全域性 SideTable 中獲取當前物件的 SideTable
    SideTable& table = SideTables()[this];

    // NO SPINLOCK HERE
    // _objc_rootIsDeallocating() is called exclusively by _objc_storeWeak(), 
    // which already acquired the lock on our behalf.


    // fixme can't do this efficiently with os_lock_handoff_s
    // if (table.slock == 0) {
    //     _objc_fatal("Do not call -_isDeallocating.");
    // }

    RefcountMap::iterator it = table.refcnts.find(this);
    return (it != table.refcnts.end()) && (it->second & SIDE_TABLE_DEALLOCATING);
}
複製程式碼
void 
objc_object::sidetable_clearDeallocating()
{
    // 從全域性 SideTable 中獲取當前物件的 SideTable
    SideTable& table = SideTables()[this];

    // clear any weak table items
    // clear extra retain count and deallocating bit
    // (fixme warn or abort if extra retain count == 0 ?)
    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
            // 如果引用計數值不為 0,而且標記為弱引用,則直接調弱引用的清空方法,清空當前物件的弱引用表
            // weak_clear_no_lock 會在後面講~~
            weak_clear_no_lock(&table.weak_table, (id)this);
        }
        // 清空表內的所有元素
        table.refcnts.erase(it);
    }
    table.unlock();
}
複製程式碼

Weak

bool 
objc_object::sidetable_isWeaklyReferenced()
{
    bool result = false;

    // 從全域性 SideTable 中獲取當前物件的 SideTable
    SideTable& table = SideTables()[this];
    table.lock();

    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        // 如果引用計數值不為 0,讀取弱引用標記位
        result = it->second & SIDE_TABLE_WEAKLY_REFERENCED;
    }

    table.unlock();

    return result;
}
複製程式碼
void 
objc_object::sidetable_setWeaklyReferenced_nolock()
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.nonpointer);
#endif

    // 從全域性 SideTable 中獲取當前物件的 SideTable
    SideTable& table = SideTables()[this];
    // 把當前物件標記為 弱引用
    table.refcnts[this] |= SIDE_TABLE_WEAKLY_REFERENCED;
}
複製程式碼

Lock

注意:以下的兩個方法都是在平臺可以使用位域的情況下才會有效的。SUPPORT_NONPOINTER_ISA = YES 時生效。

// 為當前物件的 SideTable 表加鎖
void 
objc_object::sidetable_lock()
{
    SideTable& table = SideTables()[this];
    table.lock();
}

// 為當前物件的 SideTable 表解鎖
void 
objc_object::sidetable_unlock()
{
    SideTable& table = SideTables()[this];
    table.unlock();
}
複製程式碼

Debug

// Used to assert that an object is not present in the side table.
bool
objc_object::sidetable_present()
{
    bool result = false;
    // 從全域性 SideTable 中獲取當前物件的 SideTable
    SideTable& table = SideTables()[this];

    table.lock();

    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) result = true;
    // 向弱引用表註冊當前物件
    // weak_is_registered_no_lock 會在後面講
    if (weak_is_registered_no_lock(&table.weak_table, (id)this)) result = true;

    table.unlock();

    return result;
}
複製程式碼

objc_weak.mm

weak_entry_insert

/** 
 * 將 new_entry 新增到物件的弱引用表中。不再檢查 referent 是否已經存在表中。
 */
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);

    // hash_pointer 就是一個 Hash Key 生成函式
    size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    // 迴圈遍歷整個弱引用陣列,找到可以放置 new_entry 的 index 位置 (找空位)
    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++;

    // 更新已經使用的 Hash Key 的數量
    if (hash_displacement > weak_table->max_hash_displacement) {
        weak_table->max_hash_displacement = hash_displacement;
    }
}
複製程式碼

weak_resize

static void weak_resize(weak_table_t *weak_table, size_t new_size)
{
    // #define TABLE_SIZE(entry) (entry->mask ? entry->mask + 1 : 0)
    // 計算弱引用表的大小
    size_t old_size = TABLE_SIZE(weak_table);

    weak_entry_t *old_entries = weak_table->weak_entries;
    // calloc : 分配記憶體且把記憶體空間的所有位置零
    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);
    }
}
複製程式碼

weak_grow_maybe & weak_compact_maybe

// Grow the given zone's table of weak references if it is full.
static void weak_grow_maybe(weak_table_t *weak_table)
{
    // #define TABLE_SIZE(entry) (entry->mask ? entry->mask + 1 : 0)
    // 計算弱引用表的大小
    size_t old_size = TABLE_SIZE(weak_table);

    // 如果雜湊表已經裝滿了 3/4 ,則讓雜湊表翻倍或分配 64 個雜湊位(64 個指標位的寬度)
    // 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);
    }
}

// Shrink the table if it is mostly empty.
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);

    // 如果表內的槽大於 1024 個,而且佔到了 1/16 表空間,則壓縮表。
    // 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
    }
}
複製程式碼

weak_entry_remove

/**
 * 從弱引用表中刪除條目。
 */
static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
{
    // remove entry
    // 如果 entry 的 referrers 已經被標記為 ness,則釋放條目的內容
    if (entry->out_of_line()) free(entry->referrers);
    // 二進位制位置零
    bzero(entry, sizeof(*entry));

    weak_table->num_entries--;
    // 壓縮表
    weak_compact_maybe(weak_table);
}
複製程式碼

weak_entry_for_referent

/** 
 * 返回給定被引用的弱引用表條目。如果沒有 referent 對應的條目,返回 NULL 。相當於執行一個查詢操作。
 *
 * @param weak_table 
 * @param referent The object. Must not be nil.
 * 
 * @return The table of weak referrers to this object. 
 */
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;

    // 得到 Hash Key
    size_t begin = hash_pointer(referent) & weak_table->mask;
    size_t index = begin;
    size_t hash_displacement = 0;
    // 迴圈遍歷 Hash 表
    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++;
        // 最大的雜湊放置值 : 即已經在表中放置了多少個條目
        // 如果發現遍歷過程中,hash_displacement 大於最大的雜湊放置值,則退出迴圈
        if (hash_displacement > weak_table->max_hash_displacement) {
            return nil;
        }
    }
    
    return &weak_table->weak_entries[index];
}
複製程式碼

grow_refs_and_insert

// 如果上面的方法都能看懂,那麼這個方法就很簡單了,我就不寫註釋了。

/** 
 * 增長 referrer 的雜湊表。重雜湊每個 referrer。
 * 
 * @param entry Weak pointer hash set for a particular object.
 */
__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);
    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);
}
複製程式碼

append_referrer

/** 
 * 將給定 referrer 新增到此條目中的弱指標集。不執行重複檢查 (b/c 弱指標不會新增兩次到集合中 )。
 *
 * @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()) {
        // 嘗試插入到 entry 的 inline_referrers 中
        // 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;
            }
        }

        // 如果無法插入,則構建一個小型的陣列儲存 new_referrer 
        // 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.
        // #define WEAK_INLINE_COUNT = 4
        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; // 3
        entry->max_hash_displacement = 0;
    }
   
    assert(entry->out_of_line());

    // 重構整個表
    // 如果真的 out_of_line 了,那麼 TABLE_SIZE(entry) = mask + 1 = 4,而且 num_refs = 4,
    // 即觸發重構表的操作 grow_refs_and_insert ,增長表且重新插入所有條目
    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
        return grow_refs_and_insert(entry, new_referrer);
    }

    // 下面的操作非常熟了吧~~~

    size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    while (entry->referrers[index] != nil) {
        hash_displacement++;
        index = (index+1) & entry->mask;
        if (index == begin) bad_weak_table(entry);
    }
    if (hash_displacement > entry->max_hash_displacement) {
        entry->max_hash_displacement = hash_displacement;
    }
    weak_referrer_t &ref = entry->referrers[index];
    ref = new_referrer;
    entry->num_refs++;
}
複製程式碼

remove_referrer

/** 
 * 如果 old_referrer 存在表中,則從 referrers 中刪除 old_referrer 。
 * 不會觸發刪除兩次的操作,因為表中不會存在兩個相同的 old_referrer。
 * 
 * @todo this is slow if old_referrer is not present. Is this ever the case? 
 *
 * @param entry The entry holding the referrers.
 * @param old_referrer The referrer to remove. 
 */
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
    if (! entry->out_of_line()) {
        // 這裡就是那個 @todo 說的導致慢的原因,迴圈遍歷嘛。
        // 查詢表中是否有 old_referrer 
        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--;
}
複製程式碼

weak_register_no_lock

/** 
 * 註冊一個新的 (物件,弱指標) 對。如果它不存在就建立一個新的弱物件條目。
 * 
 * @param weak_table 全域性的弱引用表
 * @param referent 被弱引用的物件
 * @param referrer 被弱引用的物件的記憶體地址
 */
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,直接返回
    // 或者 
    // 物件的 isa 沒有使用 Tagged Pointer (位域 技術),也返回。
    if (!referent  ||  referent->isTaggedPointer()) return referent_id;

    // ensure that the referenced object is viable
    bool deallocating;
    // 通過物件地址找到對應的類 (RR: retain/release) 
    // hasCustomRR: 是否有自定義實現 retain/release 方法
    if (!referent->ISA()->hasCustomRR()) {
        // 如果沒有自定義釋放方法,則通過 rootIsDeallocating 得到物件是否正在銷燬
        deallocating = referent->rootIsDeallocating();
    }
    else {
        // typedef 是我專門加的,方便檢視程式碼~
        typedef BOOL (*AllowsWeakReference)(objc_object *, SEL);
   
        // 獲取 allowsWeakReference 方法的函式地址
        AllowsWeakReference allowsWeakReference =
            (AllowsWeakReference) object_getMethodImplementation((id)referent, 
                                                                 SEL_allowsWeakReference);
        // 如果沒找到,直接返回 nil
        if ((IMP)allowsWeakReference == _objc_msgForward) {
            return nil;
        }
        // 呼叫 allowsWeakReference 方法得到物件是否正在銷燬的值
        deallocating =
            ! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
    }

    // 如果物件正在銷燬 
    if (deallocating) {
        // 如果需要讓程式 Crash 
        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;
    // 從弱引用表中查詢當前物件的弱引用條目,如果能夠找到(已經新增過弱引用了),則
    // 通過 append_referrer 嘗試插入新的弱引用條目,並更新條目內容
    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_unregister_no_lock

/** 
 * 登出一個已經存在的弱引用。
 * 當銷燬引用的記憶體且引用還在的時候,就使用這個方法。(換句話說,0 引用隨後會變成一個壞記憶體訪問)
 * 
 * Does nothing if referent/referrer is not a currently active weak reference.
 * Does not zero referrer.
 * 
 * FIXME currently requires old referent value to be passed in (lame)
 * FIXME unregistration should be automatic if referrer is collected
 * 
 * @param weak_table The global weak table.
 * @param referent The object.
 * @param referrer The weak reference.
 */
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);
        // 標記是否需要從表中移除 entry
        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.

複製程式碼

weak_is_registered_no_lock

#if DEBUG
bool
weak_is_registered_no_lock(weak_table_t *weak_table, id referent_id) 
{
    return weak_entry_for_referent(weak_table, (objc_object *)referent_id);
}
#endif
複製程式碼

weak_clear_no_lock

/** 
 * 被 dealloc 呼叫;把所有提供的物件的弱引用指標置 nil,這樣它們將不會再被使用了。
 * 
 * @param weak_table 弱引用表
 * @param referent The object being deallocated. 需要銷燬物件
 */
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;
    
    // 如果您還記得 weak_entry_t 的資料結構應該很容易明白,這裡會出現兩種情況
    // 第一種,是越界的處理方式,第二種是沒有越界的處理方式
    if (entry->out_of_line()) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry); // entry->mask + 1,陣列的大小
    } 
    else { // mini Array
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT; // 4
    }
    
    for (size_t i = 0; i < count; ++i) {
        objc_object **referrer = referrers[i];
        if (referrer) { 
            // 如果有弱引用值,則置 nil
            if (*referrer == referent) {
                *referrer = nil;
            }
            else if (*referrer) {
                // 下面的資訊是說明,如果沒有找到弱引用值(二級指標),則是錯誤地使用了
                // objc_storeWeak() and objc_loadWeak() 方法導致的結果。
                _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);
}
複製程式碼

第四部分:storeWeak & 總結

storeWeak 方法

// NSObject.mm

template <HaveOld haveOld, HaveNew haveNew, CrashIfDeallocating crashIfDeallocating>

// 全域性方法,靜態資料區
// 構建 weak 引用結構
static id storeWeak(id *location, objc_object *newObj /* 新值 */)
{
    assert(haveOld  ||  haveNew);
    if (!haveNew) assert(newObj == nil);

    Class previouslyInitializedClass = nil;
    // 舊值
    id oldObj;
    // 記憶體管理表 (不是單純的 weak 表哦!)
    SideTable *oldTable;
    SideTable *newTable;

    // 為舊值和新值獲取鎖。
    // 按地址獲取鎖,防止獲取鎖出現問題。
    // 如果舊值在我們底部發生了變化,請重試。
 retry:
    // 如果物件有舊值,就從記憶體管理表中獲取舊值,否則直接置 nil
    if (haveOld) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    // 如果有新值,就從記憶體管理表中獲取新值,否則置 nil
    if (haveNew) {
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

    // lockTwo 是根據誰有值就調誰的鎖,觸發加鎖 ( C++ 方法過載),如果兩個都有值,那麼兩個都加鎖咯!
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);

    if (haveOld  &&  *location != oldObj) {
        // 如果有舊值,而且 *location 不是舊值的記憶體地址,則進行解鎖操作 
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        // 回去 retry 標籤處,再來一次,反正一直到正確為止,這裡才會跳過 (就是一個迴圈)
        goto retry;
    }

    // 通過確保沒有弱引用物件是否有『 -/+initialized 』的 isa 指標,
    // 來防止弱引用機制和『 +initialized 』機制之間發生死鎖。
    if (haveNew  &&  newObj) {
        // 獲取物件的 isa 指標,因為 isa 指標是 objc_class 類的第一個成員,
        // 即 isa 指標的地址值就是 Class 的地址值。
        Class cls = newObj->getIsa();
        // 因為這裡有迴圈結構 goto retry ,所以會出現前一個初始化的類這個概念
        // 如果不是已經初始化的類,則加鎖並初始化類
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            // 加鎖
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            // 呼叫物件所在類的(不是元類)初始化方法,
            // 即 呼叫的是 [newObjClass initialize]; 類方法
            _class_initialize(_class_getNonMetaClass(cls, (id)newObj));

            // 如果這個類在這個執行緒中完成了 +initialize 的任務,那麼這很好。
            // 如果這個類還在這個執行緒中繼續執行著 +initialize 任務,
            // (比如,這個類的例項在呼叫 storeWeak 方法,而 storeWeak 方法呼叫了 +initialize .)
            // 這樣我們可以繼續執行,但在上面它將進行初始化和尚未初始化的檢查。
            // 相反,在重試時設定 previouslyInitializedClass 為這個類來識別它。
            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;
}
複製程式碼

總結

1、弱引用物件儲存在那?

  • 程式中所有的弱引用物件儲存在 StripMap<SideTable *>
  • 某個物件的弱引用物件儲存在當前物件的 SideTable 中的 weak_table_t
  • 某個物件的單個弱引用物件儲存在 wea_entry_treferent 成員中

2、弱引用物件在什麼時候會被自動置 nil ?

物件 dealloc 時會呼叫 weak_clear_no_lock 方法對錶中的弱引用物件進行置 nil 操作。

3、怎樣標記物件是一個弱引用物件的?

使用位域技術,儲存在當前物件的引用計數值的二進位制位中,標記位是第 0 位 (SIDE_TABLE_WEAKLY_REFERENCED) 。

相關文章