[iOS開發]weak底層原理

語言: CN / TW / HK

@[toc]

Retain、release複習

我們在Strong實現部分了;瞭解過了retain和release的原始碼 先拿個圖扔這複習一下 在這裡插入圖片描述

請新增圖片描述

release這裡應該是 在這裡插入圖片描述

請新增圖片描述

詳解見這個部落格 [iOS開發]ARC

關於引用計數的儲存方式,清楚看來有兩種,一種是通過isa,另一種是通過SideTable 來詳細學習一下

SideTable


HashMap(雜湊表) 基於陣列的一種資料結構,通過一定的演算法,把key進行運算得出一個數字,用這個數字做陣列下標,將value存入這個下標對應的記憶體之中


HashTon (雜湊桶) 雜湊演算法算出的數字有可能會重複,對於雜湊值重複的資料,如何存入雜湊表呢?常用方法有閉雜湊和開雜湊等方式,其中採用開雜湊方式的雜湊表稱為雜湊桶。==開雜湊就是在雜湊值對應的位置上,使用連結串列或陣列,將雜湊值衝突的資料存入這個連結串列或者陣列中,提高查詢效率==


為了管理所有物件的引用計數和weak指標,蘋果建立了一個全域性的SideTables,雖然名字後面又個"s",但其不過==還是一個全域性的Hash桶,裡面的內容裝的都是SideTable結構體而已==。它使用物件的記憶體地址當它的key。==來管理引用計數和weak指標==。

看一下SideTable的內部

objectivec struct SideTable { spinlock_t slock; //自旋鎖 RefcountMap refcnts; //存放引用計數 weak_table_t weak_table; //weak_table是一個雜湊 先學一下SideTable中的這三個成員變數

spinlock_t slock 自旋鎖

我們學習過了作業系統 鎖是執行緒同步時一個重要的工具 作業系統中有五大鎖 - 訊號量: - - 整型訊號量S,S<=0表示該資源已被佔用,S>0表示該資源可用,pv操作進行訪問 - - 記錄型訊號量 s.value > 0 表示該資源可用的數目;< 0表示在等待連結串列中已經阻塞的數目 - - AND型訊號量,AND型訊號量是指同時需要多個資源且每種佔用一個資源時的訊號量操作。 - - 訊號量集 對應有多種資源,相當於記錄型的集合 - 互斥量:和二元訊號量類似,唯一不同的是,互斥量的獲取和釋放必須是在同一個執行緒中進行的。如果一個執行緒去釋放一個不是其所佔有的訊號量是無效的。而訊號量是可以由其他執行緒釋放的。 - 臨界區:併發執行的程序中,訪問臨界資源的必須互斥執行的程式段叫臨界區 - 讀寫鎖:解決讀者寫者問題產生的鎖 - 條件變數:條件變數相當於一種通知機制。多個執行緒可以設定等待該條件變數,而一旦另外的執行緒設定了該條件變數(相當於喚醒條件變數)後,多個等待的執行緒就可以繼續執行了。

分離鎖、拆分鎖

因為==物件引用計數相關操作應該是原子性的==。不然如果多個執行緒同時去寫一個物件的引用計數,那就會造成資料錯亂,失去了記憶體管理的意義。同時又因為記憶體中物件的數量是很大的,需要非常頻繁的操作SideTables,所以不能對整個Hash表加鎖。==蘋果採用了分離鎖技術==

  • 分拆鎖 (lock splitting) 和分離鎖 (lock striping) 是降低執行緒請求鎖的頻率從而達到降低鎖競爭的兩種方式。相互獨立的狀態變數,應該使用獨立的鎖進行保護。但有時開發者會錯誤的使用一個鎖保護所有的狀態變數。對於這些鎖需要仔細分配,以降低發生死鎖的風險
  • 如果一個鎖守護多個相互獨立的狀態變數,你可能能夠通過分拆鎖,使每一個鎖守護不同的變數。這樣可以使每一個鎖被請求的頻率都變小了。分拆鎖對於中等競爭強度的鎖,能夠有效的把它們大部分轉化為非競爭的鎖,使效能和可可伸縮性都得到了提高。
  • 分拆鎖有時候可以被擴充套件,分成若干加鎖塊的集合,並且它們歸屬於相互獨立的物件,這種情況就是分離鎖。

我們將每個SideTable裡的每個物件的引用計數都加一把鎖,這就是分拆鎖,雖然安全 但是消耗很大

我們給每個SideTable加上一把鎖,只讓某個SideTable不能多次訪問,這就是分離鎖

自旋鎖

自旋鎖和互斥鎖

  • 相同點:都能保證同一時間只有一個執行緒訪問共享資源。都能保證執行緒安全。
  • 不同點:
    • 互斥鎖:如果共享資料已經有其他執行緒加鎖了,執行緒會進入休眠狀態等待鎖。一旦被訪問的資源被解鎖,則等待資源的執行緒會被喚醒。
    • 自旋鎖:如果共享資料已經有其他執行緒加鎖了,執行緒會以死迴圈的方式等待鎖,一旦被訪問的資源被解鎖,則等待資源的執行緒會立即執行。
  • 自旋鎖的效率高於互斥鎖。但是我們要注意由於自旋時不釋放CPU,因而持有自旋鎖的執行緒應該儘快釋放自旋鎖,否則等待該自旋鎖的執行緒會一直在哪裡自旋,這就會浪費CPU時間。
  • 在操作引用計數的時候對SideTable加鎖,避免資料錯誤

蘋果的選擇

對於每個SideTable,中間都有自旋鎖 同樣也使用了分離鎖給單個的SideTable上鎖

安全+效率很合理

RefcountMap

來了解一下這個圖 請新增圖片描述

DisguisedPtr<objc_object>為key的hash表,用來儲存OC物件的引用計數 不知道DisguisedPtr<objc_object>是什麼,但是我們已經對retain中儲存引用計數的方式十分清晰了,如果未開啟isa優化 或 在isa優化情況下isa_t的extra_rc引用計數加一後向上溢位了,才會存入這個雜湊表中。

weak_table_t weak_table

儲存物件弱引用指標的hash表。weak功能實現的核心資料結構。

看一下wewak_table_t objectivec struct weak_table_t { weak_entry_t *weak_entries; //連續地址空間的頭指標, 陣列 //管理所有指向某物件的weak指標,也是一個hash size_t num_entries; //陣列中已佔用位置的個數 uintptr_t mask; //陣列下標最大值(即陣列大小 -1) uintptr_t max_hash_displacement; //最大雜湊偏移值 }; weak_table_t中並沒有直接通過陣列存放weak指標,而是通過結構體來存放weak指標 兩個引數 - location:__weak指標的地址,儲存指標的地址,這樣便可以再最後將其指向的物件置nil - newObj: 所引用的物件

objectivec struct weak_entry_t { DisguisedPtr<objc_object> referent; //被指物件的地址。前面迴圈遍歷查詢的時候就是判斷目標地址是否和他相等。 union { struct { weak_referrer_t *referrers; //可變陣列,裡面儲存著所有指向這個物件的弱引用的地址。當這個物件被釋放的時候,referrers裡的所有指標都會被設定成nil。 //雜湊的目的是清除一個weak指標 //指向 referent 物件的 weak 指標陣列 uintptr_t out_of_line_ness : 2; //這裡標記是否超過內聯邊界, 下面會提到 uintptr_t num_refs : PTR_MINUS_2; //陣列中已佔用的大小 uintptr_t mask; //陣列下標最大值(陣列大小 - 1) 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]; //只有4個元素的陣列,預設情況下用它來儲存弱引用的指標。當大於4個的時候使用referrers來儲存指標。 //當指向這個物件的weak指標不超過4個,則直接使用陣列inline_referrers,省去hhash }; }; union共用體 也是提醒我們蘋果是使用同一段記憶體去存放不同的資訊

中間有兩個陣列 weak_referrer_t *referrersweak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; 在weak指標個數小於4的時候會存入第二個陣列,省去了hash,提高了儲存效率,大於4的時候才會存入referrers當中

構造和解構函式

objectivec // 建構函式 SideTable() { memset(&weak_table, 0, sizeof(weak_table)); } //解構函式(看看函式體,蘋果設計的SideTable其實不希望被析構,不然會引起fatal 錯誤) ~SideTable() { _objc_fatal("Do not delete SideTable."); } 所以SideTable🈲️析構(解構函式的作用並不是刪除物件,而是在撤銷物件佔用的記憶體之前完成一些清理工作。)

最後是鎖的操作

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

小小總結一下SideTable

在這裡插入圖片描述

weak部分

簡單申請一個__weak修飾符修飾的變數 我們檢視一下彙編 在這裡插入圖片描述 說到底只有兩個部分

我們看一下對應的原始碼部分

objc_initWeak

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

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

} ``` 檢視物件例項是否有效 無效物件直接導致指標的釋放

如果有效那麼就會呼叫objc_storeWeak()函式

objc_storeWeak

看一下store函式上面的註釋 (直接放漢語版的了) objectivec 更新弱變數 如果haveOld為true,則變數有舊值,舊值需要被清理,這個值可能是nil[該weak指標之前已經有了指向] 如果haveNew為true,則有一個新值需要被分配到變數,這個值可能是nil 如果CrashIfDeallocating為true,且如果newObj正在解除分配或newObj的類不支援弱引用時,程序將停止 如果CrashIfDeallocating為false,則儲存nil 再來看一下其中的具體邏輯與判斷 ``` template static id storeWeak(id location, objc_object newObj) {

assert(haveOld  ||  haveNew);
if (!haveNew) assert(newObj == nil);

// 該過程用來更新弱引用指標的指向 // 初始化previouslyInitializedClass指標 Class previouslyInitializedClass = nil; id oldObj; SideTable oldTable; SideTable newTable;

// 模版函式,haveOld和haveNew由編譯器決定傳入的值,location是weak指標,newObj是weak指標將要指向的物件 retry: if (haveOld) { // 更改指標,獲得oldObj 為索引所儲存的值地址 oldObj = *location; oldTable = &SideTables()[oldObj]; } else { oldTable = nil; } if (haveNew) { // 更改新值指標,獲得以newObj為索引所儲存的值地址 newTable = &SideTables()[newObj]; } else { newTable = nil; }

// 加鎖操作,防止多執行緒中競爭衝突 SideTable::lockTwo(oldTable, newTable); // 避免執行緒衝突重處理 // location應該與oldObj保持一致,如果不同,說明當前的location已經處理過oldObj 可是又被其他執行緒所修改 if (haveOld && *location != oldObj) { SideTable::unlockTwo(oldTable, newTable); goto retry; }

// 防止弱引用間死鎖 // 並且通過+initialize初始化構造器保證所有弱引用的isa非空指向 if (haveNew && newObj) { //獲得新物件的isa指標 Class cls = newObj->getIsa(); // 判斷isa非空且已經初始化 if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized()) { SideTable::unlockTwo(oldTable, newTable); //對其isa指標進行初始化 _class_initialize(_class_getNonMetaClass(cls, (id)newObj)); //如果該類已經完成執行+initialize方法是最理想情況 //如果該類+initialize線上程中 //例如+initialize正在呼叫storeWeak方法 //需要手動對其增加保護策略,並設定previouslyInitializedClass指標進行標記 previouslyInitializedClass = cls; //重新嘗試 goto retry; } }

// Clean up old value, if any.
if (haveOld) {
    weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}

if (haveNew) {
//如果weak指標將要指向新值,在weak_table中處理賦值操作
    newObj = (objc_object *)
        weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                              crashIfDeallocating);
//如果弱引用被釋放的weak_register_no_lock方法返回nil
//在引用計數表中設定若引用標記位
    if (newObj  &&  !newObj->isTaggedPointer()) {
        //弱引用位初始化操作
        //方法修改weak新引用的物件的bit標誌位
        newObj->setWeaklyReferenced_nolock();
    }

    // 之前不要設定location物件, 這裡需要更改指標指向
    *location = (id)newObj;
}
else {
    // 沒有新值,則無需修改
}

SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
return (id)newObj;

} `` -storeWeak通過接受了3個引數haveOld、haveNew和crashIfDeallocation,這三個引數是以模版函式的方式傳入的。 - - haveOld:weak指標之前是否指向了一個弱引用 - - haveNew:weak指標是否需要指向一個新的引用 - - 如果被弱引用的物件正在析構,此時再弱引用該物件是否應該crash - - 初始化的時候傳入的值應該是0 1 1 - 同時其維護了兩張表oldTablenewTable兩張表分別表示舊的弱引用表和新的弱引用表,他們都是SideTable的hash表 - 如果weak指標之前指向了一個弱引用,則會呼叫weak_unregister_no_lock 方法將舊的weak指標地址移除 - 如果weak指標需要指向一個新的引用 - - 則會呼叫weak_register_no_lock`方法將新的weak指標地址新增到弱引用表中 - - 如果弱引用被釋放的weak_register_no_lock方法返回nil,在引用計數表中設定弱引用標記位

weak_register_no_lock將新的weak指標新增到弱引用表

```objectivec id weak_register_no_lock(weak_table_t weak_table, id referent_id, id referrer_id, bool crashIfDeallocating) { objc_object referent = (objc_object )referent_id; objc_object referrer = (objc_object )referrer_id;

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

// ensure that the referenced object is viable
bool deallocating;
if (!referent->ISA()->hasCustomRR()) {
    deallocating = referent->rootIsDeallocating();
}
else {
    BOOL (*allowsWeakReference)(objc_object *, SEL) = 
        (BOOL(*)(objc_object *, SEL))
        object_getMethodImplementation((id)referent, 
                                       SEL_allowsWeakReference);
    if ((IMP)allowsWeakReference == _objc_msgForward) {
        return nil;
    }
    deallocating =
        ! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
}

if (deallocating) {
    if (crashIfDeallocating) {
        _objc_fatal("Cannot form weak reference to instance (%p) of "
                    "class %s. It is possible that this object was "
                    "over-released, or is in the process of deallocation.",
                    (void*)referent, object_getClassName((id)referent));
    } else {
        return nil;
    }
}

// now remember it and where it is being stored
weak_entry_t *entry; //如果 weak_table 有對應的 entry
if ((entry = weak_entry_for_referent(weak_table, referent))) {//返回給定引用物件的弱引用表項。如果referent沒有條目,則返回NULL。執行查詢。
    append_referrer(entry, referrer); //將 weak 指標存入對應的 entry 中,官方翻譯:將給定的引用新增到此條目中的弱指標集。不執行重複檢查(不會將b/c弱指標新增到集合中兩次)。
} 
else {
    weak_entry_t new_entry(referent, referrer); //建立新的 entry
    weak_grow_maybe(weak_table); //檢視是否需要調整 weak_table 中 weak_entries 陣列大小
    weak_entry_insert(weak_table, &new_entry); //將新的 entry 插入到 weak_table 中,官方:將新的_項新增到物件的弱引用表中。不檢查引用物件是否已在表中。
}

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

return referent_id;

} `` - 傳入了4個引數 - -weak_tableweak_table_t結構體型別 我們之前已經有了解 - -referent_id:weak指標指向的物件 - -*referrer_id: weak指標的地址,操作時需要用到這個指標的地址 - -crashIfDeallocating:如果被弱引用的物件正在析構,此時再弱引用該物件是否應該crash - 主要流程 1. 如果referent(就是weak指標)為nil或referent採用了TaggedPointer計數方式,直接返回,不做任何操作 2. 如果正在析構,丟擲異常 3. 如果物件不能被weak引用,直接返回nil 4. 如果物件不屬於上面的情況,則呼叫weak_entry_for_referent方法,根據弱引用物件的地址從弱引用表中找到對應的weak_entry(指向其物件的指標),如果能夠找到則呼叫append_referrer`方法向其中插入weak指標地址。否則就新建一個weak_entry

核心有兩個對應的函式

weak_entry_for_referent取元素

```objectivec static weak_entry_t * weak_entry_for_referent(weak_table_t weak_table, objc_object referent) { assert(referent);

weak_entry_t *weak_entries = weak_table->weak_entries;

if (!weak_entries) return nil;

size_t begin = hash_pointer(referent) & weak_table->mask;  // 這裡通過 & weak_table->mask的位操作,來確保index不會越界
size_t index = begin;
size_t hash_displacement = 0;
while (weak_table->weak_entries[index].referent != referent) {
    index = (index+1) & weak_table->mask;
    if (index == begin) bad_weak_table(weak_table->weak_entries); // 觸發bad weak table crash
    hash_displacement++;
    if (hash_displacement > weak_table->max_hash_displacement) { // 當hash衝突超過了可能的max hash 衝突時,說明元素沒有在hash表中,返回nil 
        return nil;
    }
}

return &weak_table->weak_entries[index];

} ```

append_referrer新增元素

```objectivec static void append_referrer(weak_entry_t entry, objc_object *new_referrer) { if (! entry->out_of_line()) { // 如果weak_entry 尚未使用動態陣列,走這裡 // 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; } }

    // 如果inline_referrers的位置已經存滿了,則要轉型為referrers,做動態陣列。
    // Couldn't insert inline. Allocate out of line.
    weak_referrer_t *new_referrers = (weak_referrer_t *)
        calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
    // This constructed table is invalid, but grow_refs_and_insert
    // will fix it and rehash it.
    for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
        new_referrers[i] = entry->inline_referrers[I];
    }
    entry->referrers = new_referrers;
    entry->num_refs = WEAK_INLINE_COUNT;
    entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;
    entry->mask = WEAK_INLINE_COUNT-1;
    entry->max_hash_displacement = 0;
}

// 對於動態陣列的附加處理:
assert(entry->out_of_line()); // 斷言: 此時一定使用的動態陣列

if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) { // 如果動態陣列中元素個數大於或等於陣列位置總空間的3/4,則擴充套件陣列空間為當前長度的一倍
    return grow_refs_and_insert(entry, new_referrer); // 擴容,並插入
}

// 如果不需要擴容,直接插入到weak_entry中
// 注意,weak_entry是一個雜湊表,key:w_hash_pointer(new_referrer) value: new_referrer

// 細心的人可能注意到了,這裡weak_entry_t 的hash演算法和 weak_table_t的hash演算法是一樣的,同時擴容/減容的演算法也是一樣的
size_t begin = w_hash_pointer(new_referrer) & (entry->mask); // '& (entry->mask)' 確保了 begin的位置只能大於或等於 陣列的長度
size_t index = begin;  // 初始的hash index
size_t hash_displacement = 0;  // 用於記錄hash衝突的次數,也就是hash再位移的次數
while (entry->referrers[index] != nil) {
    hash_displacement++;
    index = (index+1) & entry->mask;  // index + 1, 移到下一個位置,再試一次能否插入。(這裡要考慮到entry->mask取值,一定是:0x111, 0x1111, 0x11111, ... ,因為陣列每次都是*2增長,即8, 16, 32,對應動態陣列空間長度-1的mask,也就是前面的取值。)
    if (index == begin) bad_weak_table(entry); // index == begin 意味著陣列繞了一圈都沒有找到合適位置,這時候一定是出了什麼問題。
}
if (hash_displacement > entry->max_hash_displacement) { // 記錄最大的hash衝突次數, max_hash_displacement意味著: 我們嘗試至多max_hash_displacement次,肯定能夠找到object對應的hash位置
    entry->max_hash_displacement = hash_displacement;
}
// 將ref存入hash陣列,同時,更新元素個數num_refs
weak_referrer_t &ref = entry->referrers[index];
ref = new_referrer;
entry->num_refs++;

} ``` 1. 首先確定是使用定長陣列還是動態陣列 2. 定長陣列直接將weak指標地址新增到陣列 3. 定長陣列已經用完,將定長陣列中的元素轉存到動態陣列中

weak_unregister_no_lock移除舊weak指標地址

```objectivec 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))) { // 查詢到referent所對應的weak_entry_t
    remove_referrer(entry, referrer);  // 在referent所對應的weak_entry_t的hash陣列中,移除referrer

    // 移除元素之後, 要檢查一下weak_entry_t的hash陣列是否已經空了
    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_t的hash陣列已經空了,則需要將weak_entry_t從weak_table中移除
        weak_entry_remove(weak_table, entry);
    }
}

``` 1. 首先它會在weak_table中找出referent對應的weak_entry_t 2. 在weak_entry_t中移除referrer 3. 移除元素後,判斷此時weak_entry_t中是否還有元素(empty == true) 4. 如果此時weak_entry_t已經沒有元素了,則需要將weak_entry_t從weak_table中移除

dealloc部分

當物件的引用計數為0時,底層會呼叫_objc_rootDealloc方法對物件進行釋放,而在_objc_rootDealloc方法裡面會呼叫rootDealloc方法。 ```objectivec inline void objc_object::rootDealloc() { if (isTaggedPointer()) return; // fixme necessary?

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

} ``` 1. 首先判斷物件是否是taggedPointer型別,如果是直接返回 2. 如果物件是採用了優化的isa計數方式,且同時滿足物件沒有被weak引用、沒有關聯物件、沒有自定義的C++析構方法、沒有用到SideTable來引用計數 則直接快速釋放 3. 如果不能滿足2,則呼叫dispose方法

```objectivec 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();
}

return obj;

} ``` 如果有自定義的C++析構方法,則呼叫解構函式。如果有關聯物件,則移除關聯物件並將其自身從Association Manager的map中移除。呼叫clearDeallocation方法清楚物件的相關引用

```objectivec 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());

} ``` 先判斷物件是否採用了優化isa引用計數,如果沒有的話則需要清理物件儲存在SideTable中的引用計數資料。 如果採用了優化isa引用計數,則判斷是否有使用SideTable的輔助引用計數(isa.has_sidetable_rc)或者有weak引用(isa.weakly_referenced),符合這兩種情況中一種的,呼叫clearDeallocating_slow 方法。

```objectivec NEVER_INLINE void objc_object::clearDeallocating_slow() { assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));

SideTable& table = SideTables()[this]; // 在全域性的SideTables中,以this指標為key,找到對應的SideTable
table.lock();
if (isa.weakly_referenced) { // 如果obj被弱引用
    weak_clear_no_lock(&table.weak_table, (id)this); // 在SideTable的weak_table中對this進行清理工作
}
if (isa.has_sidetable_rc) { // 如果採用了SideTable做引用計數
    table.refcnts.erase(this); // 在SideTable的引用計數中移除this
}
table.unlock();

} `` 呼叫了weak_clear_no_lock`方法來做weak_table的清理工作

```objectivec 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); // 找到referent在weak_table中對應的weak_entry_t
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引用referent的weak 指標地址陣列以及陣列長度
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]; // 取出每個weak ptr的地址
    if (referrer) {
        if (*referrer == referent) { // 如果weak ptr確實weak引用了referent,則將weak ptr設定為nil,這也就是為什麼weak 指標會自動設定為nil的原因
            *referrer = nil;
        }
        else if (*referrer) { // 如果所儲存的weak ptr沒有weak 引用referent,這可能是由於runtime程式碼的邏輯錯誤引起的,報錯
            _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); // 由於referent要被釋放了,因此referent的weak_entry_t也要移除出weak_table

} ```

結論

runtime維護了一個weak表。用於儲存指向某個物件的所有weak指標。weak表其實是一個weak_table_t結構的hash表,key是所指物件的地址,value是weak指標的地址陣列

weak實現的原理包括以下三步: 1. 初始化時,runtime會對其使用objc_initWeak函式,初始化一個新的weak指標指向物件的地址 2. 新增引用時:objc_initWeak函式會呼叫objc_storeWeak()函式,objc_storeWeak()的作用是用於更新指標指向,建立弱引用表。 3. 釋放時,呼叫clearDeallocating函式。clearDeallocating函式首先根據物件地址獲取所有weak指標陣列,然後遍歷這個陣列,把其中的資料設為nil,最後把這個entry從weak表中刪除,最後清理物件的記錄。