weak的實現(xiàn)原理

weak功能就不多說了,它的實現(xiàn)原理就從一段代碼開始吧。
一個OC變量的默認(rèn)屬性都是strong孵运,所以我們?nèi)绻枰?code>weak屬性的變量就需要顯示的標(biāo)記出來顺又。

int main(int argc, char * argv[]) {
    @autoreleasepool {
        NSObject *obj1 = [[NSObject alloc]init];
        __weak NSObject *obj2 = obj1;
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

我們把上面這段代碼轉(zhuǎn)換成C++就會變成這個樣子

...//一堆的函數(shù)、屬性、結(jié)構(gòu)體等等的定義返弹,不是我們關(guān)注的重點
...
//main函數(shù)的c++實現(xiàn)
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        NSObject *obj1 = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
        __attribute__((objc_ownership(weak))) NSObject *obj2 = obj1;
        return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
    }
}

然后在 return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));上打上斷點锈玉,進(jìn)入?yún)R編語言嘲玫,我們可以看到main函數(shù)的匯編實現(xiàn),摘取一些看得懂的蛙吏,可以看出一些函數(shù)的調(diào)用棧

    0x1047397d9 <+25>:  callq  0x10473b37a               ; symbol stub for: objc_autoreleasePoolPush
    0x1047397de <+30>:  movq   0x534b(%rip), %rdi        ; (void *)0x0000000104fa3170: NSObject
    0x1047397e9 <+41>:  callq  0x10473b36e               ; symbol stub for: objc_alloc
    0x1047397ee <+46>:  movq   0x51d3(%rip), %rsi        ; "init"
    0x1047397f5 <+53>:  movq   0x382c(%rip), %rdi        ; (void *)0x0000000104c5a800: objc_msgSend
    0x104739815 <+85>:  callq  0x10473b392               ; symbol stub for: objc_initWeak
    0x104739821 <+97>:  movq   0x5310(%rip), %rcx        ; (void *)0x000000010473ec28: AppDelegate
    0x104739828 <+104>: movq   0x5209(%rip), %rdx        ; "class"
    0x104739853 <+147>: callq  0x10473b350               ; symbol stub for: NSStringFromClass
    0x104739858 <+152>: movq   %rax, -0x68(%rbp)
    0x104739865 <+165>: callq  0x10473b3b0               ; symbol stub for: objc_retainAutoreleasedReturnValue
    0x104739881 <+193>: callq  0x10473b356               ; symbol stub for: UIApplicationMain
    0x10473989b <+219>: callq  *0x378f(%rip)             ; (void *)0x0000000104c57d70: objc_release
    0x1047398a8 <+232>: callq  0x10473b386               ; symbol stub for: objc_destroyWeak
    0x1047398b8 <+248>: callq  0x10473b3b6               ; symbol stub for: objc_storeStrong
    0x1047398c1 <+257>: callq  0x10473b374               ; symbol stub for: objc_autoreleasePoolPop  

匯編語言實在看不懂泼诱,不過通過上面三段代碼的對比我們大概知道,初始化obj1之后舷蒲,賦值給weak屬性的obj2時耸袜,調(diào)用了objc_initWeak函數(shù)。
可以在objc4源碼中找到objc_initWeak實現(xiàn)牲平,可以看到就是一個簡單的判斷之后調(diào)用了storeWeak函數(shù)堤框,storeWeak函數(shù)中才真正實現(xiàn)了weak引用。

  • storeWeak函數(shù)工作流程
  1. 在進(jìn)行真正的引用工作之前欠拾,先要做好一些列的準(zhǔn)備工作:
    • weak指針有指向的對象胰锌, 先獲取weak指針原有對象的SideTable引用計數(shù)表;
    • 若被引用newObj有值藐窄, 則獲取newObj的引用計數(shù)表资昧;
    • 對上面兩個引用計數(shù)表加鎖;
    • 判斷值是否被修改荆忍,如若被修改格带,解鎖撤缴,重新開始;
    • 判斷被引用對象是否完成isa指針初始化叽唱,如果沒完成屈呕,解鎖,重新開始
  2. 在準(zhǔn)備工作完成之后棺亭,會調(diào)用weak_unregister_no_lock()方法來從原有的表中先刪除這個weak指針虎眨。
  3. 然后再調(diào)用weak_register_no_lock()來向?qū)?yīng)的表中插入這個weak指針;把被應(yīng)用對象設(shè)置為弱引用表镶摘;把被引用對象的地址賦值給weak指針指向的地址嗽桩。
  4. 解鎖兩個引用表,完成weak引用凄敢。
//定義了一個函數(shù)模版
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;
    //用于暫存location指向的地址
    id oldObj;
    //location原值指向的引用計數(shù)表
    SideTable *oldTable;
    //newObj指向的引用計數(shù)表
    SideTable *newTable;

    // Acquire locks for old and new values.
    // Order by lock address to prevent lock ordering problems. 
    // Retry if the old value changes underneath us.
    ///同時獲取舊值和新值的鎖
    ///按鎖地址排序以防止鎖排序問題
    ///如果舊值有修改,那么跳回retry重新做一遍流程
 retry:
    if (haveOld) {
        oldObj = *location;
        //獲取location原指向地址的引用計數(shù)表
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
        //獲取newObj的引用計數(shù)表
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }
    //對兩個引用計數(shù)表加鎖
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);

    //如果location有值而且location != oldObj,那就是location的值被改變了(因為oldObj = *location;這一步已經(jīng)賦值給oldObj,理論上應(yīng)該是相等的)涝缝,需要解鎖扑庞,從頭開始
    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        goto retry;
    }

    // Prevent a deadlock between the weak reference machinery
    // and the +initialize machinery by ensuring that no 
    // weakly-referenced object has an un-+initialized isa.
    //為了防止弱引用機(jī)制和初始化機(jī)制之間的死鎖,
    //我們要保證被弱引用的對象isa指針已經(jīng)完成初始化工作
    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));

            // If this class is finished with +initialize then we're good.
            // If this class is still running +initialize on this thread 
            // (i.e. +initialize called storeWeak on an instance of itself)
            // then we may proceed but it will appear initializing and 
            // not yet initialized to the check above.
            // Instead set previouslyInitializedClass to recognize it on retry.
            previouslyInitializedClass = cls;

            goto retry;
        }
    }

    // Clean up old value, if any.
    //把location原有的弱引用清除掉
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // Assign new value, if any.
    if (haveNew) {
        //將location添加到newObj的弱引用表中
        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
        //如果添加弱引用被拒絕拒逮,weak_register_no_lock會返回nil
        
        // Set is-weakly-referenced bit in refcount table.
        // 將newObj設(shè)置為被弱引用狀態(tài)
        if (newObj  &&  !newObj->isTaggedPointer()) {
            newObj->setWeaklyReferenced_nolock();
        }

        // Do not set *location anywhere else. That would introduce a race.
        // 只能在這里把location指向新值(newObj)
        *location = (id)newObj;
    }
    else {
        // No new value. The storage is not changed.
    }
    //對兩個引用表解鎖
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
    
    return (id)newObj;
}

上面這段代碼就是runtime實現(xiàn)weak引用的全部過程罐氨,其中有幾個數(shù)據(jù)結(jié)構(gòu)非常重要,分別是SideTables消恍,SideTable岂昭,weak_table_tweak_entry_t。它們和對象的引用計數(shù)狠怨,以及weak引用相關(guān)约啊。

先說一下這四個數(shù)據(jù)結(jié)構(gòu)的關(guān)系。 SideTables是一個64個元素長度的hash數(shù)組佣赖,里面存儲了SideTable恰矩。SideTables的hash鍵值就是一個對象obj的address。
因此可以說憎蛤,一個obj外傅,對應(yīng)了一個SideTable。但是一個SideTable俩檬,會對應(yīng)多個obj萎胰。因為SideTable的數(shù)量只有64個,所以會有很多obj共用同一個SideTable棚辽。

而在一個SideTable中技竟,有三個成員,分別是

spinlock_t slock;  //自旋鎖屈藐,用于對SideTable操作時將其鎖定
RefcountMap refcnts;  //對象引用計數(shù)相關(guān)信息
weak_table_t weak_table;  //對象的弱引用相關(guān)信息

其中榔组,refcents是一個hash map熙尉,其key是obj的地址,而value搓扯,則是obj對象的引用計數(shù)检痰。而weak_table則存儲了弱引用obj的指針的地址,其本質(zhì)是一個以obj地址為key锨推,弱引用obj的指針的地址作為value的hash表铅歼。hash表的節(jié)點類型是weak_entry_t。

引用計數(shù)Hash表.png

SideTables

先來說一下最外層的SideTables爱态。SideTables可以理解為一個全局的hash數(shù)組谭贪,里面存儲了SideTable類型的數(shù)據(jù),其長度為64锦担。

但是SideTabls并不是一個被定義的數(shù)據(jù)類型,它只是一個全局靜態(tài)函數(shù)慨削,返回值是一個StripedMap類型洞渔,所以其實SideTables類型就是StripedMap類型

static StripedMap<SideTable>& SideTables() {
    return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}

看一下StripedMap類的數(shù)據(jù)結(jié)構(gòu)

//參數(shù)模版化,這里討論的T為SideTable
template<typename T>
class StripedMap {
    // TARGET_OS_IPHONE 目前寫死為0缚态,所以StripeCount只能是64
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
    enum { StripeCount = 8 };
#else
    enum { StripeCount = 64 };
#endif

    struct PaddedT {
        // T(模版參數(shù)磁椒,這里就是SideTable) 64字節(jié)對齊
        T value alignas(CacheLineSize);
    };
    
    //所有PaddedT struct 類型數(shù)據(jù)被存儲在一個長度為64的array數(shù)組中
    PaddedT array[StripeCount];

    // 該方法以void *作為key 來獲取void *對應(yīng)在StripedMap中的下標(biāo)位置
    static unsigned int indexForPointer(const void *p) {
        uintptr_t addr = reinterpret_cast<uintptr_t>(p);
        return ((addr >> 4) ^ (addr >> 9)) % StripeCount;// % StripeCount 是為了防止下標(biāo)越界
    }

 public:
    //獲取void *對應(yīng)的SideTable
    T& operator[] (const void *p) { 
        return array[indexForPointer(p)].value; 
    }
    const T& operator[] (const void *p) const { 
        return const_cast<StripedMap<T>>(this)[p]; 
    }

    ....//一些其他操作
};

這里的邏輯代碼寫的很清晰,我后面省略了一系列的鎖操作玫芦,可以自己在runtime的代碼里看一下浆熔。
從中可以看到,所有的StripedMap鎖操作桥帆,最終是調(diào)用的array[i].value的相關(guān)操作医增。因此,對于模版參數(shù)T類型老虫,必須具備相關(guān)的lock操作接口叶骨。
因此,要作為StripedMap哈希表的模版參數(shù)祈匙,對于T類型還是有所要求的(就是能夠進(jìn)行鎖操作)忽刽。而在SideTables中,T即為SideTable類型夺欲。

SideTable

SideTable的定義很清晰跪帝,有三個成員:

  • spinlock_t slock : 自旋鎖,用于上鎖/解鎖 SideTable些阅。
  • RefcountMap refcnts :以DisguisedPtr<objc_object>為key的哈希表伞剑,用來存儲OC對象的引用計數(shù)(僅在未開啟isa優(yōu)化 或 在isa優(yōu)化情況下isa_t的引用計數(shù)溢出時才會用到)。
  • weak_table_t weak_table : 存儲對象弱引用指針的哈希表扑眉。是OC weak功能實現(xiàn)的核心數(shù)據(jù)結(jié)構(gòu)纸泄。

除了三個成員外赖钞,蘋果為SideTable還寫了構(gòu)造和析構(gòu)函數(shù),在析構(gòu)函數(shù)的源碼中可以看出來聘裁,SideTable是不能被析構(gòu)的雪营。

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

最后是一堆鎖操作,用于多線程訪問SideTable衡便, 同時献起,也符合我們上面提到的StripedMap中關(guān)于T模版參數(shù)的lock接口定義。

struct SideTable {
    spinlock_t slock;  // 自旋鎖镣陕,防止多線程訪問沖突
    RefcountMap refcnts; // 對象引用計數(shù)表
    weak_table_t weak_table; // 對象弱引用表

    //構(gòu)造函數(shù)
    SideTable() {
        memset(&weak_table, 0, sizeof(weak_table));
    }
    
    //析構(gòu)函數(shù)--不能析構(gòu)
    ~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.
    // 按鎖地址順序?qū)蓚€SideTable加鎖谴餐,以防止鎖排序問題
    template<HaveOld, HaveNew>
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template<HaveOld, HaveNew>
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

spinlock_t slock

spinlock_t的最終定義實際上是一個uint32_t類型的非公平的自旋鎖。所謂非公平呆抑,就是說獲得鎖的順序和申請鎖的順序無關(guān)岂嗓,也就是說,第一個申請鎖的線程有可能會是最后一個獲得到該鎖鹊碍,或者是剛獲得鎖的線程會再次立刻獲得到該鎖厌殉,造成饑餓等待。 同時侈咕,在OC中公罕,_os_unfair_lock_opaque也記錄了獲取它的線程信息,只有獲得該鎖的線程才能夠解開這把鎖耀销。

typedef struct os_unfair_lock_s {
    uint32_t _os_unfair_lock_opaque;
} os_unfair_lock, *os_unfair_lock_t;

關(guān)于自旋鎖的實現(xiàn)楼眷,蘋果并未公布,但是大體上應(yīng)該是通過操作_os_unfair_lock_opaque 這個uint32_t的值熊尉,當(dāng)大于0時罐柳,鎖可用,當(dāng)?shù)扔诨蛐∮?時帽揪,需要鎖等待硝清。

RefcountMap refcnts

可以看這個
RefcountMap refcnts 用來存儲OC對象的引用計數(shù)。它實質(zhì)上是一個以objc_object為key的哈希表转晰,其vaule就是OC對象的引用計數(shù)芦拿。同時,當(dāng)OC對象的引用計數(shù)變?yōu)?時查邢,會自動將相關(guān)的信息從哈希表中剔除蔗崎。
RefcountMap的本質(zhì)是一個DenseMap類,本文中的調(diào)用模板的三個類型參數(shù)DisguisedPtr<objc_object>扰藕,size_t缓苛, true 分別表示DenseMaphash key類型,value類型,是否允許引用計數(shù)為0的節(jié)點被使用未桥。

template<typename KeyT, typename ValueT,
         bool ZeroValuesArePurgeable = false, 
         typename KeyInfoT = DenseMapInfo<KeyT> >
class DenseMap
    : public DenseMapBase<DenseMap<KeyT, ValueT, ZeroValuesArePurgeable, KeyInfoT>,
                          KeyT, ValueT, KeyInfoT, ZeroValuesArePurgeable>
  • ZeroValuesArePurgeable
    默認(rèn)值是false, 但RefcountMap指定其初始化為true笔刹。 這個成員標(biāo)記是否可以使用值為 0 (引用計數(shù)為 1) 的桶。 因為空桶存的初始值就是 0冬耿,所以值為 0 的桶和空桶沒什么區(qū)別舌菜。如果允許使用值為 0 的桶, 查找桶時如果沒有找到對象對應(yīng)的桶亦镶,也沒有找到墓碑桶日月,就會優(yōu)先使用值為 0 的桶。
  • Buckets
    指針管理一段連續(xù)內(nèi)存空間缤骨,也就是數(shù)組爱咬,數(shù)組成員是BucketT類型的對象,我們這里將BucketT對象稱為桶(實際上這個數(shù)組才應(yīng)該叫桶绊起,蘋果把數(shù)組中的元素稱為桶應(yīng)該是為了形象一些精拟,而不是哈希桶中的桶的意思)。桶數(shù)組在申請空間后勒庄,會進(jìn)行初始化串前,在所有位置上都放上空桶(桶的 key 為EmptyKey時是空桶),之后對引用計數(shù)的操作实蔽,都要依賴于桶。
    桶的數(shù)據(jù)類型實際上是std::pair谨读,類似于swift中的元祖類型局装,就是將對象地址和對象的引用計數(shù)(這里的引用計數(shù)類似于 isa,也是使用其中的幾個 bit 來保存引用計數(shù)劳殖,留出幾個 bit 來做其它標(biāo)記位)組合成一個數(shù)據(jù)類型铐尚。
  • NumEntries
    記錄數(shù)組中已使用的非空的桶的個數(shù)。
  • NumTombstones
    Tombstone直譯為墓碑, 當(dāng)一個對象的引用計數(shù)為0哆姻,要從桶中取出時宣增,其所處的位置會被標(biāo)記為TombstoneNumTombstones就是數(shù)組中的墓碑的個數(shù)矛缨。后面會介紹到墓碑的作用爹脾。
  • NumBuckets
    桶的數(shù)量,因為數(shù)組中始終都充滿桶箕昭,所以可以理解為數(shù)組大小灵妨。

RefcountMap 的工作邏輯

  1. 通過計算對象地址的哈希值, 來從SideTables中獲取對應(yīng)的 SideTable. 哈希值重復(fù)的對象的引用計數(shù)存儲在同一個 SideTable 里.
  2. SideTable 使用 find() 方法和重載 [] 運算符的方式, 通過對象地址來確定對象對應(yīng)的桶. 最終執(zhí)行到的查找算法是 LookupBucketFor().
  3. 查找算法會先對桶的個數(shù)進(jìn)行判斷, 如果桶數(shù)為 0 則 return false 回上一級調(diào)用插入方法. 如果查找算法找到空桶或者墓碑桶, 同樣 return false 回上一級調(diào)用插入算法, 不過會先記錄下找到的桶. 如果找到了對象對應(yīng)的桶, 只需要對其引用計數(shù)+ 1 或者- 1. 如果引用計數(shù)為 0 需要銷毀對象, 就將這個桶中的 key 設(shè)置為 TombstoneKey
  4. 插入算法會先查看可用量, 如果哈希表的可用量(墓碑桶+空桶的數(shù)量)小于 1/4, 則需要為表重新開辟更大的空間, 如果表中的空桶位置少于 1/8 (說明墓碑桶過多), 則需要清理表中的墓碑. 以上兩種情況下哈希查找算法會很難查找正確位置, 甚至可能會產(chǎn)生死循環(huán), 所以要先處理表, 處理表之后還會重新分配所有桶的位置, 之后重新查找當(dāng)前對象的可用位置并插入. 如果沒有發(fā)生以上兩種情況, 就直接把新的對象的引用計數(shù)放入調(diào)用者提供的桶里.
bool LookupBucketFor(const LookupKeyT &Val,
                       const BucketT *&FoundBucket) const {
    ...
    if (NumBuckets == 0) { //桶數(shù)是0
      FoundBucket = 0;
      return false; //返回 false 回上層調(diào)用添加函數(shù)
    }
    ...
    unsigned BucketNo = getHashValue(Val) & (NumBuckets-1); //將哈希值與數(shù)組最大下標(biāo)按位與
    unsigned ProbeAmt = 1; //哈希值重復(fù)的對象需要靠它來重新尋找位置
    while (1) {
      const BucketT *ThisBucket = BucketsPtr + BucketNo; //頭指針 + 下標(biāo), 類似于數(shù)組取值
      //找到的桶中的 key 和對象地址相等, 則是找到
      if (KeyInfoT::isEqual(Val, ThisBucket->first)) {
        FoundBucket = ThisBucket;
        return true;
      }
      //找到的桶中的 key 是空桶占位符, 則表示可插入
      if (KeyInfoT::isEqual(ThisBucket->first, EmptyKey)) { 
        if (FoundTombstone) ThisBucket = FoundTombstone; //如果曾遇到墓碑, 則使用墓碑的位置
        FoundBucket = FoundTombstone ? FoundTombstone : ThisBucket;
        return false; //找到空占位符, 則表明表中沒有已經(jīng)插入了該對象的桶
      }
      //如果找到了墓碑
      if (KeyInfoT::isEqual(ThisBucket->first, TombstoneKey) && !FoundTombstone)
        FoundTombstone = ThisBucket;  // 記錄下墓碑
      //這里涉及到最初定義 typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap, 傳入的第三個參數(shù) true
      //這個參數(shù)代表是否可以清除 0 值, 也就是說這個參數(shù)為 true 并且沒有墓碑的時候, 會記錄下找到的 value 為 0 的桶
      if (ZeroValuesArePurgeable  && 
          ThisBucket->second == 0  &&  !FoundTombstone) 
        FoundTombstone = ThisBucket;

      //用于計數(shù)的 ProbeAmt 如果大于了數(shù)組容量, 就會拋出異常
      if (ProbeAmt > NumBuckets) {
          _objc_fatal("...");
      }
      BucketNo += ProbeAmt++; //本次哈希計算得出的下表不符合, 則利用 ProbeAmt 尋找下一個下標(biāo)
      BucketNo&= (NumBuckets-1); //得到新的數(shù)字和數(shù)組下標(biāo)最大值按位與
    }
  }

weak_table_t weak_table

weak_table_t weak_table 用來存儲OC對象弱引用的相關(guān)信息。我們知道落竹,SideTables一共只有64個節(jié)點泌霍,而在我們的APP中,一般都會不只有64個對象述召,因此朱转,多個對象一定會重用同一個SideTable節(jié)點蟹地,也就是說,一個weak_table會存儲多個對象的弱引用信息藤为。因此在一個SideTable中怪与,又會通過weak_table作為哈希表再次分散存儲每一個對象的弱引用信息。
weak_table_t是一個哈希表的結(jié)構(gòu), 根據(jù)對象的地址計算哈希值, 哈希值相同的對象按照下標(biāo) +1 的形式向后查找可用位置, 是典型的閉散列算法. 最大哈希偏移值即是所有對象中計算出的哈希值和實際插入位置的最大偏移量, 在查找時可以作為循環(huán)的上限.

/**
 * The global weak references table. Stores object ids as keys,
 * and weak_entry_t structs as their values.
 *
 * 全局若引用表凉蜂,以objct為key琼梆,weak_entry_t做為值
 */
struct weak_table_t {
    weak_entry_t *weak_entries; //hash數(shù)組,用來存儲弱引用對象的相關(guān)信息weak_entry_t
    size_t    num_entries;  // hash數(shù)組中的元素個數(shù)
    uintptr_t mask;  // hash數(shù)組長度-1窿吩,會參與hash計算茎杂。(注意,這里是hash數(shù)組的長度纫雁,而不是元素個數(shù)煌往。比如,數(shù)組長度可能是64轧邪,而元素個數(shù)僅存了2個)
    uintptr_t max_hash_displacement; // 可能會發(fā)生的hash沖突的最大次數(shù)
};

通過對象的地址刽脖,可以在weak_table_t中找到對應(yīng)的 weak_entry_tweak_entry_t中保存了所有指向這個對象的弱引用信息忌愚。
尋找的過程主要在weak_entry_for_referent()函數(shù)中:

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; // 根據(jù)ref的地址獲取hash值曲管,然后和mask按位與運算,保證不越界
    size_t index = begin;
    size_t hash_displacement = 0; // hash沖突位移次數(shù)
    while (weak_table->weak_entries[index].referent != referent) { // 循環(huán)判斷weak_entry_t中的referent 是否與要找的referent相同
        index = (index+1) & weak_table->mask; // 下標(biāo)+1并和mask按位與運算硕糊,保證數(shù)組不越界
        if (index == begin) bad_weak_table(weak_table->weak_entries); // 回到初始位置還沒找到對應(yīng)的referent院水,說明weak_table有問題,拋出異常
        hash_displacement++; // 位移次數(shù)+1
        if (hash_displacement > weak_table->max_hash_displacement) {
            //位移超過hash沖突最大次數(shù)简十,說明沒找到對應(yīng)的weak_entry_t檬某,返回空
            return nil;
        }
    }
    return &weak_table->weak_entries[index];
}

weak_entry_t中使用了一個共用體, 當(dāng)指向這個對象的weak指針不超過 4 個, 則直接使用數(shù)組inline_referrers螟蝙,省去了哈希操作的步驟恢恼,如果weak指針個數(shù)超過了 4 個,就要使用第一個結(jié)構(gòu)體中的哈希表胰默。第一個結(jié)構(gòu)體的結(jié)構(gòu)和weak_table_t很像场斑,同樣也是一個哈希表,其存儲的元素是weak_referrer_t初坠,實質(zhì)上是弱引用該對象的指針的指針和簸,即objc_object **new_referrer, 通過操作指針的指針碟刺,就可以使得weak引用的指針在對象析構(gòu)后锁保,指向nil

struct weak_entry_t {
    DisguisedPtr<objc_object> referent; // 被引用的對象
    
    // 引用該對象的弱引用共用體。
    // 引用個數(shù)小于等于4爽柒,用inline_referrers數(shù)組吴菠。
    // 引用個數(shù)大于4,用哈希數(shù)組weak_referrer_t *referrers
    union {
        struct {
            weak_referrer_t *referrers;       // 弱引用該對象的指針地址的哈希數(shù)組
            uintptr_t        out_of_line_ness : 2;   // 是否使用動態(tài)哈希數(shù)組標(biāo)記位
            uintptr_t        num_refs : PTR_MINUS_2; // 哈希數(shù)組中的元素個數(shù)
            uintptr_t        mask;  // 哈希數(shù)組長度-1浩村,會參與hash計算做葵。(和weak_table_t的mask一樣)。
            uintptr_t        max_hash_displacement;  // 可能會發(fā)生的hash沖突的最大次數(shù)
        };
        struct {
            // out_of_line_ness field is low bits of inline_referrers[1]
            // 弱引用數(shù)量不超過4個時存放弱引用指針的數(shù)組
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };
    
    //是否使用動態(tài)哈希數(shù)組
    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;
        }
    }
};

weak_entry_t的核心功能就是就是weak指針的增加和刪除功能心墅,看一下增加功能--append_referrer()


/**
 *
 * 將給定的引用添加到entry中的弱指針集合中酿矢。不查重(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()) {
        //判斷out_of_line_ness標(biāo)記怎燥,是否已經(jīng)使用哈希數(shù)組
        //如果還沒使用就直接循環(huán)數(shù)組瘫筐,找到空位置,把弱引用指針添加進(jìn)去
        // 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;
            }
        }
        
        //如果數(shù)組中已經(jīng)滿了, 就要使用動態(tài)哈希數(shù)組referrers了
        //從這里開始, 這一段是把inline_referrers數(shù)組調(diào)整為使用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];
        }
        //配置weak_entry_t的參數(shù)
        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());
    //根據(jù)哈希數(shù)組的規(guī)則铐姚,使用量超過填裝因子(一般0.7-0.8)策肝,就要擴(kuò)容
    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) { // 數(shù)組使用量超過3/4
        return grow_refs_and_insert(entry, new_referrer); //需要擴(kuò)展數(shù)組并配置entry信息
    }
    size_t begin = w_hash_pointer(new_referrer) & (entry->mask); //使用哈希算法計算到一個起始下標(biāo)
    size_t index = begin;
    size_t hash_displacement = 0; //哈希偏移次數(shù)
    while (entry->referrers[index] != nil) {
        hash_displacement++; // 便宜次數(shù)+1
        index = (index+1) & entry->mask; // 下標(biāo)值+1蹲蒲,并與mask安位與運算
        if (index == begin) bad_weak_table(entry); //如果找了一圈沒找到空位置斩狱,說明這個entry有問題,拋出異常
    }
    if (hash_displacement > entry->max_hash_displacement) { //判斷位移數(shù)是否大于原有的哈希沖突次數(shù)蝇率,如果超過就把新的偏移數(shù)重新賦值給哈希沖突數(shù)
        entry->max_hash_displacement = hash_displacement;
    }
    // 把弱引用指針添加到referrers數(shù)組
    weak_referrer_t &ref = entry->referrers[index];
    ref = new_referrer;
    // 引用次數(shù)+1
    entry->num_refs++;
}

接著是刪除功能--remove_referrer()依许,基本上也和增加功能沒什么區(qū)別:

static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
    if (! entry->out_of_line()) { //判斷out_of_line_ness標(biāo)記棺禾,是否已經(jīng)使用哈希數(shù)組
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            // 循環(huán)判斷,找到對應(yīng)的ref峭跳,將其置空
            if (entry->inline_referrers[i] == old_referrer) {
                entry->inline_referrers[i] = nil;
                return;
            }
        }
        //如果循環(huán)一遍還沒找到對應(yīng)的弱引用帘睦,說明出bug了,拋出異常
        _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;
    }
    // 使用hash數(shù)組的情況
    
    size_t begin = w_hash_pointer(old_referrer) & (entry->mask); // 根據(jù)old_referrer 找到初始下標(biāo)
    size_t index = begin;
    size_t hash_displacement = 0; // 哈希沖突位移次數(shù)
    while (entry->referrers[index] != old_referrer) {
        index = (index+1) & entry->mask; // 位移次數(shù)和mask按位與運算坦康,保證不越界
        if (index == begin) bad_weak_table(entry); //循環(huán)一圈未找到,說明有bug
        hash_displacement++; // 位移次數(shù)+1
        if (hash_displacement > entry->max_hash_displacement) {
            // 如果位移次數(shù)超過了entry標(biāo)記的最大沖突次數(shù)诡延,說明有問題滞欠,拋出異常
            _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;
        }
    }
    // 刪除下標(biāo)對應(yīng)的弱引用
    entry->referrers[index] = nil;
    // 引用次數(shù)-1
    entry->num_refs--;
}

總結(jié)

調(diào)用storeWeak()函數(shù) --> 獲取oldTablenewTable兩個引用計數(shù)表 --> 調(diào)用weak_unregister_no_lock()函數(shù)刪除掉oldObj的弱引用 --> 先調(diào)用weak_entry_for_referent()找到弱引用信息表 --> 在調(diào)用remove_referrer()刪除弱引用信息 --> 調(diào)用weak_register_no_lock()把新的弱引用信息添加到newObj的弱引用表 --> 調(diào)用weak_entry_for_referent()找到newObj的弱引用信息表 --> 調(diào)用append_referrer()location添加到弱引用表中 --> 把newObj賦值給location

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末肆良,一起剝皮案震驚了整個濱河市筛璧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌惹恃,老刑警劉巖夭谤,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異巫糙,居然都是意外死亡朗儒,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來醉锄,“玉大人乏悄,你說我怎么就攤上這事】也唬” “怎么了檩小?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長烟勋。 經(jīng)常有香客問我规求,道長,這世上最難降的妖魔是什么卵惦? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任阻肿,我火速辦了婚禮,結(jié)果婚禮上鸵荠,老公的妹妹穿的比我還像新娘冕茅。我一直安慰自己,他們只是感情好蛹找,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布姨伤。 她就那樣靜靜地躺著,像睡著了一般庸疾。 火紅的嫁衣襯著肌膚如雪乍楚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天届慈,我揣著相機(jī)與錄音徒溪,去河邊找鬼。 笑死金顿,一個胖子當(dāng)著我的面吹牛臊泌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播揍拆,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼渠概,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了嫂拴?” 一聲冷哼從身側(cè)響起播揪,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎筒狠,沒想到半個月后猪狈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡辩恼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年雇庙,在試婚紗的時候發(fā)現(xiàn)自己被綠了谓形。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡状共,死狀恐怖套耕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情峡继,我是刑警寧澤冯袍,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站碾牌,受9級特大地震影響康愤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜舶吗,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一征冷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧誓琼,春花似錦检激、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至傲隶,卻和暖如春饺律,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背跺株。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工复濒, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人乒省。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓巧颈,卻偏偏與公主長得像,于是被迫代替她去往敵國和親袖扛。 傳聞我的和親對象是個殘疾皇子洛二,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容