主頁 > 移動端開發 > [iOS開發]weak底層原理

[iOS開發]weak底層原理

2021-08-02 08:11:02 移動端開發

文章目錄

  • Retain、release復習
  • SideTable
    • spinlock_t slock 自旋鎖
      • 分離鎖、拆分鎖
      • 自旋鎖
      • 蘋果的選擇
    • RefcountMap
    • weak_table_t weak_table
    • 構造和解構式
    • 最后是鎖的操作
    • 小小總結一下SideTable
  • weak部分
    • objc_initWeak
      • objc_storeWeak
      • weak_register_no_lock將新的weak指標添加到弱參考表
        • weak_entry_for_referent取元素
        • append_referrer添加元素
      • weak_unregister_no_lock移除舊weak指標地址
  • dealloc部分
    • 結論

Retain、release復習

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

請添加圖片描述

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

請添加圖片描述

詳解見這個博客
[iOS開發]ARC

關于參考計數的存盤方式,清楚看來有兩種,一種是通過isa,另一種是通過SideTable
來詳細學習一下

SideTable


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


HashTon (哈希桶)
哈希演算法算出的數字有可能會重復,對于哈希值重復的資料,如何存入哈希表呢?常用方法有閉散列和開散列等方式,其中采用開散列方式的哈希表稱為哈希桶,開散列就是在哈希值對應的位置上,使用鏈表或陣列,將哈希值沖突的資料存入這個鏈表或者陣列中,提高查找效率


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

看一下SideTable的內部

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

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: 所參考的物件
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當中

構造和解構式

// 建構式
    SideTable() {
        memset(&weak_table, 0, sizeof(weak_table));
    }
    //解構式(看看函式體,蘋果設計的SideTable其實不希望被析構,不然會引起fatal 錯誤)
    ~SideTable() {
        _objc_fatal("Do not delete SideTable.");
  }

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

小小總結一下SideTable

在這里插入圖片描述

weak部分

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

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

objc_initWeak

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函式上面的注釋
(直接放漢語版的了)

更新弱變數
如果haveOld為true,則變數有舊值,舊值需要被清理,這個值可能是nil[該weak指標之前已經有了指向]
如果haveNew為true,則有一個新值需要被分配到變數,這個值可能是nil
如果CrashIfDeallocating為true,且如果newObj正在解除分配或newObj的類不支持弱參考時,行程將停止
如果CrashIfDeallocating為false,則儲存nil

再來看一下其中的具體邏輯與判斷

template <HaveOld haveOld, HaveNew haveNew,
          CrashIfDeallocating crashIfDeallocating>
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<haveOld, haveNew>(oldTable, newTable);
//        避免執行緒沖突重處理
//        location應該與oldObj保持一致,如果不同,說明當前的location已經處理過oldObj 可是又被其他執行緒所修改
    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        goto retry;
    }

// 防止弱參考間死鎖
// 并且通過+initialize初始化構造器保證所有弱參考的isa非空指向
    if (haveNew  &&  newObj) {
    //獲得新物件的isa指標
        Class cls = newObj->getIsa();
        // 判斷isa非空且已經初始化
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            SideTable::unlockTwo<haveOld, haveNew>(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指標添加到弱參考表

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取元素

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添加元素

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指標地址

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方法,

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方法
//dispose中主要呼叫了這個方法
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方法清楚物件的相關參考

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 方法,

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的清理作業

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表中洗掉,最后清理物件的記錄,

在這里插入圖片描述

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/291332.html

標籤:其他

上一篇:Flutter 與 Compose怎么選?小孩子才做選擇

下一篇:Android四大組件及變異測驗

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more