逐步探究ObjC的Weak技術底層

前言

以前的文章有說過 Atomic 原子操做的原理,其做爲一個特殊的修飾前綴,影響了存取操做。c++

在屬性修飾定義中,還有另外一類修飾前綴,他們分別是 strong weak assign copy,這些又有什麼區別呢?macos

平時喜歡探究的同窗,可能也見過 unsafe_unretained,這個又是什麼呢?數組

讓咱們從屬性修飾入手,逐步揭開弱引用的面紗。bash


原理

屬性自動生成的實現方法是怎麼樣的?

首先咱們先建立一個示例代碼文件做爲樣本。markdown

#import <Foundation/Foundation.h>

@interface PropertyObject : NSObject

@property (nonatomic, strong) NSObject *pStrongObj; //強引用
@property (nonatomic, copy)   NSObject *pCopyObj;   //拷貝
@property (nonatomic, weak)   NSObject *pWeakObj;   //弱引用
@property (nonatomic, assign) NSObject *pAssignObj; //申明
@property (nonatomic, unsafe_unretained) NSObject *pUnretainedObj; //非持有

@end

@implementation PropertyObject
@end
複製代碼

而後經過 clang -rewrite-objc -fobjc-arc -stdlib=libc++ -mmacosx-version-min=10.14 -fobjc-runtime=macosx-10.14 -Wno-deprecated-declarations main.m 命令將其解釋成 c++ 代碼。(注意這裏要指定版本,否則weak屬性不能翻譯)數據結構

展開的代碼比較多,我這裏截取關鍵部分探討。app

struct PropertyObject_IMPL {
	NSObject *__strong _pStrongObj;
	NSObject *__strong _pCopyObj;
	NSObject *__weak _pWeakObj;
	NSObject *__unsafe_unretained _pAssignObj;
	NSObject *__unsafe_unretained _pUnretainedObj;
};

{"pStrongObj","T@\"NSObject\",&,N,V_pStrongObj"},
{"pCopyObj","T@\"NSObject\",C,N,V_pCopyObj"},
{"pWeakObj","T@\"NSObject\",W,N,V_pWeakObj"},
{"pAssignObj","T@\"NSObject\",N,V_pAssignObj"},
{"pUnretainedObj","T@\"NSObject\",N,V_pUnretainedObj"}
複製代碼

從變量結構體的描述和特性能夠看出,strongcopy實際都是__strong修飾,但特性不一樣,assignunsafe_unretained 則徹底一致,都是__unsafe_unretainedweak則單獨使用__weak修飾。ide

下面咱們來看一下方法具體實現。函數

// @implementation PropertyObject

//根據偏移取值和賦值
static NSObject * _I_PropertyObject_pStrongObj(PropertyObject * self, SEL _cmd) { return (*(NSObject *__strong *)((char *)self + OBJC_IVAR_$_PropertyObject$_pStrongObj)); }
static void _I_PropertyObject_setPStrongObj_(PropertyObject * self, SEL _cmd, NSObject *pStrongObj) { (*(NSObject *__strong *)((char *)self + OBJC_IVAR_$_PropertyObject$_pStrongObj)) = pStrongObj; }

static NSObject * _I_PropertyObject_pCopyObj(PropertyObject * self, SEL _cmd) { return (*(NSObject *__strong *)((char *)self + OBJC_IVAR_$_PropertyObject$_pCopyObj)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

//只有Copy不一樣,setter的實現是objc_setProperty
static void _I_PropertyObject_setPCopyObj_(PropertyObject * self, SEL _cmd, NSObject *pCopyObj) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct PropertyObject, _pCopyObj), (id)pCopyObj, 0, 1); }

static NSObject * _I_PropertyObject_pWeakObj(PropertyObject * self, SEL _cmd) { return (*(NSObject *__weak *)((char *)self + OBJC_IVAR_$_PropertyObject$_pWeakObj)); }
static void _I_PropertyObject_setPWeakObj_(PropertyObject * self, SEL _cmd, NSObject *pWeakObj) { (*(NSObject *__weak *)((char *)self + OBJC_IVAR_$_PropertyObject$_pWeakObj)) = pWeakObj; }

static NSObject * _I_PropertyObject_pAssignObj(PropertyObject * self, SEL _cmd) { return (*(NSObject *__unsafe_unretained *)((char *)self + OBJC_IVAR_$_PropertyObject$_pAssignObj)); }
static void _I_PropertyObject_setPAssignObj_(PropertyObject * self, SEL _cmd, NSObject *pAssignObj) { (*(NSObject *__unsafe_unretained *)((char *)self + OBJC_IVAR_$_PropertyObject$_pAssignObj)) = pAssignObj; }

static NSObject * _I_PropertyObject_pUnretainedObj(PropertyObject * self, SEL _cmd) { return (*(NSObject *__unsafe_unretained *)((char *)self + OBJC_IVAR_$_PropertyObject$_pUnretainedObj)); }
static void _I_PropertyObject_setPUnretainedObj_(PropertyObject * self, SEL _cmd, NSObject *pUnretainedObj) { (*(NSObject *__unsafe_unretained *)((char *)self + OBJC_IVAR_$_PropertyObject$_pUnretainedObj)) = pUnretainedObj; }
// @end
複製代碼

在代碼中,只有copy修飾屬性的setter方法使用了objc_setProperty,其餘幾種都是根據 self + 偏移量 的方式計算出內存地址直接進行存取。oop

那問題來了,若是真的是那麼簡單的話,arc 是怎麼實現根據不一樣修飾從而進行內存管理的呢?

原來經過 clang -rewrite-objc 的代碼只是翻譯成 c++ 語言,在以後的編譯過程當中會進一步處理。

接着使用 clang -S -fobjc-arc -emit-llvm main.m -o main.ll 命令生成中間碼。

(中間碼顯示比較雜亂,我根據本身理解整理成簡潔版)

//代碼整理後
id [PropertyObject pStrongObj] {
  return *location; 
}
void [PropertyObject setPStrongObj:](self, _cmd, obj) {
  @llvm.objc.storeStrong(*location, obj)
}

id [PropertyObject pCopyObj] {
  return @objc_getProperty(self, _cmd, offset, atomic)
}
void [PropertyObject setPCopyObj:](self, _cmd, obj) {
  @objc_setProperty_nonatomic_copy(self, _cmd, obj, offset)
}

id [PropertyObject pWeakObj] {
  id obj = @llvm.objc.loadWeakRetained(*location)
  return @llvm.objc.autoreleaseReturnValue(obj)
}
void [PropertyObject setPWeakObj:](self, _cmd, obj) {
  @llvm.objc.storeWeak(*location, obj)
}

id [PropertyObject pAssignObj] {
  return *location
}
void [PropertyObject setPAssignObj:](self, _cmd, obj) {
  *location = obj
}

id [PropertyObject pUnretainedObj] {
  return *location
}
void [PropertyObject setPUnretainedObj:](self, _cmd, obj) {
  *location = obj
}
複製代碼

能夠看出分別針對strongweak 都作了處理,而assignunsafe_unretained則不作內存管理直接返回,這也說明這二者的處理方式是同樣的,區別在於 assign 針對。

strong copy weak assign unsafe_unretained
Ownership __strong __strong __weak __unsafe_unretained __unsafe_unretained
Getter *location objc_getProperty loadWeakRetained *location *location
Setter storeStrong objc_setProperty storeWeak *location *location
對象 NSObject NSObject NSObject NSObject Scalar

Weak對象怎麼實現存取的?

本文篇幅有限,暫不介紹 storeStrongobjc_setProperty_nonatomic_copy,主要介紹 weak 相關操做。

打開 objc4-750 開源代碼,翻到 NSObject.mm,咱們來一探究竟。

// 初始化弱引用
id objc_initWeak(id *location, id newObj) {
    // 不存在則不保存
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}
// 銷燬弱引用
void objc_destroyWeak(id *location) {
    (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
        (location, nil);
}
// 交換原有的值
id objc_storeWeak(id *location, id newObj) {
    return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object *)newObj);
}
複製代碼

能夠看到 runtime 中調用的都是一個方法,區別在於使用了不一樣的模版,那麼咱們來看下對一個地址的存取方法。

// 獲取操做的具體實現
id objc_loadWeakRetained(id *location) {
    id obj;
    id result;
    Class cls;

    SideTable *table;
    
 retry:
    // 保證地址有數據且不是僞指針
    obj = *location;
    if (!obj) return nil;
    if (obj->isTaggedPointer()) return obj;
    // 根據地址取出對應的表
    table = &SideTables()[obj];
    // 加鎖
    table->lock();
    // 若是數據被其餘線程改變,則重試
    if (*location != obj) {
        table->unlock();
        goto retry;
    }
    result = obj;

    cls = obj->ISA();
    if (! cls->hasCustomRR()) {
        // 若是使用的是系統默認的內存管理,則保證了已經初始化
        // 因此能夠直接rootTryRetain
        assert(cls->isInitialized());
        if (! obj->rootTryRetain()) {
            result = nil;
        }
    } else {
        // 若是不是默認的,則須要確保在初始化線程上執行自定義retain操做
        if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) {
            BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL))
                class_getMethodImplementation(cls, SEL_retainWeakReference);
            if ((IMP)tryRetain == _objc_msgForward) {
                result = nil;
            } else if (! (*tryRetain)(obj, SEL_retainWeakReference)) {
                result = nil;
            }
        } else {
            table->unlock();
            _class_initialize(cls);
            goto retry;
        }
    }
    //完成後解鎖
    table->unlock();
    return result;
}
// 保存操做的具體實現
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;
}
複製代碼

除去保護方法,其實 objc_loadWeakRetained 方法就是檢查後返回 *location,也就是變量指向的實際地址。

storeWeak 方法則是根據模版,對舊對象執行 weak_unregister_no_lock,對新對象執行 weak_register_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.
    // 上面爲蘋果註釋,看這意思應該是objc_storeWeak還須要使用引用地址作後續處理。
}
//註冊引用
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;   //引用人
    // taggedPointer沒有引用計數,不須要處理
    if (!referent  ||  referent->isTaggedPointer()) return referent_id;

    // 保證被引用人不在釋放中,否則閃退
    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;
        }
    }

    //獲取被引用人的引用數組,沒有則建立
    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;
}
//釋放過程清空引用
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) {
        //這裏應該確定有entry,由於調用前判斷了對象的WeaklyReferenced
        //若是確實沒有,蘋果認爲多是CF/objc緣由
        return;
    }

    //清空引用數組
    weak_referrer_t *referrers;
    size_t count;
    
    if (entry->out_of_line()) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    //遍歷數組,找到每一個引用人,清空他們的指向地址
    for (size_t i = 0; i < count; ++i) {
        objc_object **referrer = referrers[i];
        if (referrer) {
            if (*referrer == referent) {
                *referrer = nil;
            } else if (*referrer) {
                _objc_inform("__weak variable at %p holds %p instead of %p. "
                             "This is probably incorrect use of "
                             "objc_storeWeak() and objc_loadWeak(). "
                             "Break on objc_weak_error to debug.\n", 
                             referrer, (void*)*referrer, (void*)referent);
                objc_weak_error();
            }
        }
    }
    //去除節點
    weak_entry_remove(weak_table, entry);
}
複製代碼

能夠發現,對申明是 __weak 的變量進行存取操做,其實都是經過被操做的對象地址查找到相應的表,而後增刪表的引用數組內容。

SideTable表怎麼設計的?

關鍵就在於怎麼申明建立表,以及這個表是怎麼設計及使用的。

// SideTables 類型申明
// 這裏之因此先使用數據的方式申明是由於考慮到加載順序的問題
alignas(StripedMap<SideTable>) static uint8_t 
    SideTableBuf[sizeof(StripedMap<SideTable>)];
// 加載image時執行初始化
static void SideTableInit() {
    new (SideTableBuf) StripedMap<SideTable>();
}
// 數組還原成StripedMap類型
static StripedMap<SideTable>& SideTables() {
    return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}

// StripedMap 的結構
enum { CacheLineSize = 64 };
template<typename T>
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
    enum { StripeCount = 8 };
#else
    enum { StripeCount = 64 };
#endif
    // 64位對齊
    struct PaddedT {
        T value alignas(CacheLineSize);
    };
    // 手機系統數組個數爲8
    PaddedT array[StripeCount];
    // 把指針地址匹配到數組的序號
    static unsigned int indexForPointer(const void *p) {
        uintptr_t addr = reinterpret_cast<uintptr_t>(p);
        return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
    }
}
複製代碼

在加載鏡像的過程當中,經過 SideTableInit 方法建立全局表數組,能夠看到手機系統是8個數組。

源碼中使用 &SideTables()[obj] 的方式,其實就是把 obj 的指針地址轉成序號獲取某一個 table,經過這種方式分散冗餘。

接着咱們看 SideTable 類的內部結構。

// 哈希散列表,使用補碼的形式把指針地址做爲Key,保存引用計數
typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap;

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

struct SideTable {
    spinlock_t slock;       // 自旋鎖
    RefcountMap refcnts;    // 引用記數表
    weak_table_t weak_table;// 弱引用表

    template<HaveOld, HaveNew>
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template<HaveOld, HaveNew>
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

struct weak_table_t {
    weak_entry_t *weak_entries;     //弱引用數組
    size_t    num_entries;          //數組個數
    uintptr_t mask;                 //計算輔助量,數值爲數組總數-1
    uintptr_t max_hash_displacement;//哈希最大偏移量
};

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

typedef DisguisedPtr<objc_object *> weak_referrer_t;
struct weak_entry_t {
    // 被引用者
    DisguisedPtr<objc_object> referent;
    union {
        // 引用者數據結構
        struct {
            // 當數量超過4個時,結構轉爲指針,每次容量滿的時候就擴容兩倍
            // 須要與數組做區分,因此有out_of_line_ness標記
            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[4];
        };
    };
};
複製代碼

SideTable 存儲的不只有對象引用計數表,還有咱們關注的弱引用表,其結構順序以下:

SideTable->weak_table_t->weak_entry_t->weak_referrer_t

爲了方便理解,我模擬一下找弱引用對象的步驟:

  1. sideTable = &SideTables()[referent] 把對象內存地址按照8取餘後找到表

  2. weakTable = &sideTable->weak_table 取出弱引用表

  3. entry = weak_entry_for_referent(weakTable, referent) 根據被引用人地址,遍歷弱引用表找出入口

  4. referrer = entry->referrers[index] 入口有特殊的數組,其中保存了全部弱引用者的對象地址

仔細一點的同窗應該發現了 weak_entry_t 中有一個聯合體,這又是怎麼操做實現的呢?

// 添加新引用者
static void append_referrer (weak_entry_t *entry, objc_object **new_referrer) {
    // 沒有超過4個,就用內斂數組
    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;
            }
        }

        // 若是超過4個了,就從數組結構轉成指針結構
        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;                //最大hash移位次數,用於優化循環
        // 因爲只有4個,會在下個判斷後執行grow_refs_and_insert初始化並插入新對象
    }
    // 斷言必然是指針結構
    assert(entry->out_of_line());
    // 若是指針數量超過3/4,就容量翻倍後再插入
    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; //下標+1後取餘
        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++;
}
複製代碼

總結

至此對於弱引用的總體結構和邏輯都清楚了,對象根據修飾符進行內存管理,若是是弱引用,則找到其引用地址的引用表操做。

反過來說,強對象被引用時在全局引用表中註冊一個節點,保存全部引用者的地址,當釋放時設置全部地址爲空。


問答

被weak修飾的對象在被釋放的時候會發生什麼?是如何實現的?知道sideTable麼?裏面的結構能夠畫出來麼?

對象被釋放時執行 obj->rootDealloc(),若是有弱引用標記,則會執行 objc_destructInstance 方法後釋放。

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

        // This order is important.
        if (cxx) object_cxxDestruct(obj);           //調用析構函數
        if (assoc) _object_remove_assocations(obj); //移除關聯對象關係
        obj->clearDeallocating();                   //處理isa
    }

    return obj;
}
inline void objc_object::clearDeallocating() {
    if (slowpath(!isa.nonpointer)) {
        // Slow path for raw pointer isa.
        sidetable_clearDeallocating();
    } else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
        // Slow path for non-pointer isa with weak refs and/or side table data.
        clearDeallocating_slow();
    }

    assert(!sidetable_present());
}
void objc_object::sidetable_clearDeallocating() {
    SideTable& table = SideTables()[this];

    // 刪除強引用和弱引用
    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
            weak_clear_no_lock(&table.weak_table, (id)this);
        }
        table.refcnts.erase(it);
    }
    table.unlock();
}
複製代碼

能夠看到在 sidetable_clearDeallocating 方法中,最後執行了 weak_clear_no_lock 清空了全部引用關係。

SideTable 表結構以下圖:


總結

weak原理是繞不開的經典課題,經過閱讀開源代碼對蘋果如何實現有了大體的瞭解,受益不淺。

閱讀過程當中還驚歎於蘋果各類花式小技巧,因爲文章篇幅有限沒來得及介紹,感興趣能夠了解一下,好比 DisguisedPtr

資料分享

Objective-C Class Ivar Layout 探索

理解 ARC 實現原理

weak 弱引用的實現方式

相關文章
相關標籤/搜索