美文网首页
weak的实现原理

weak的实现原理

作者: 随风流逝 | 来源:发表于2019-12-25 14:04 被阅读0次

    weak功能就不多说了,它的实现原理就从一段代码开始吧。
    一个OC变量的默认属性都是strong,所以我们如果需要weak属性的变量就需要显示的标记出来。

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

    我们把上面这段代码转换成C++就会变成这个样子

    ...//一堆的函数、属性、结构体等等的定义,不是我们关注的重点
    ...
    //main函数的c++实现
    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]));上打上断点,进入汇编语言,我们可以看到main函数的汇编实现,摘取一些看得懂的,可以看出一些函数的调用栈

        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时,调用了objc_initWeak函数。
    可以在objc4源码中找到objc_initWeak实现,可以看到就是一个简单的判断之后调用了storeWeak函数,storeWeak函数中才真正实现了weak引用。

    • storeWeak函数工作流程
    1. 在进行真正的引用工作之前,先要做好一些列的准备工作:
      • weak指针有指向的对象, 先获取weak指针原有对象的SideTable引用计数表;
      • 若被引用newObj有值, 则获取newObj的引用计数表;
      • 对上面两个引用计数表加锁;
      • 判断值是否被修改,如若被修改,解锁,重新开始;
      • 判断被引用对象是否完成isa指针初始化,如果没完成,解锁,重新开始
    2. 在准备工作完成之后,会调用weak_unregister_no_lock()方法来从原有的表中先删除这个weak指针。
    3. 然后再调用weak_register_no_lock()来向对应的表中插入这个weak指针;把被应用对象设置为弱引用表;把被引用对象的地址赋值给weak指针指向的地址。
    4. 解锁两个引用表,完成weak引用。
    //定义了一个函数模版
    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原值指向的引用计数表
        SideTable *oldTable;
        //newObj指向的引用计数表
        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原指向地址的引用计数表
            oldTable = &SideTables()[oldObj];
        } else {
            oldTable = nil;
        }
        if (haveNew) {
            //获取newObj的引用计数表
            newTable = &SideTables()[newObj];
        } else {
            newTable = nil;
        }
        //对两个引用计数表加锁
        SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
    
        //如果location有值而且location != oldObj,那就是location的值被改变了(因为oldObj = *location;这一步已经赋值给oldObj,理论上应该是相等的),需要解锁,从头开始
        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.
        //为了防止弱引用机制和初始化机制之间的死锁,
        //我们要保证被弱引用的对象isa指针已经完成初始化工作
        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设置为被弱引用状态
            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实现weak引用的全部过程,其中有几个数据结构非常重要,分别是SideTablesSideTableweak_table_tweak_entry_t。它们和对象的引用计数,以及weak引用相关。

    先说一下这四个数据结构的关系。 SideTables是一个64个元素长度的hash数组,里面存储了SideTableSideTables的hash键值就是一个对象obj的address。
    因此可以说,一个obj,对应了一个SideTable。但是一个SideTable,会对应多个obj。因为SideTable的数量只有64个,所以会有很多obj共用同一个SideTable

    而在一个SideTable中,有三个成员,分别是

    spinlock_t slock;  //自旋锁,用于对SideTable操作时将其锁定
    RefcountMap refcnts;  //对象引用计数相关信息
    weak_table_t weak_table;  //对象的弱引用相关信息
    

    其中,refcents是一个hash map,其key是obj的地址,而value,则是obj对象的引用计数。而weak_table则存储了弱引用obj的指针的地址,其本质是一个以obj地址为key,弱引用obj的指针的地址作为value的hash表。hash表的节点类型是weak_entry_t。

    引用计数Hash表.png

    SideTables

    先来说一下最外层的SideTablesSideTables可以理解为一个全局的hash数组,里面存储了SideTable类型的数据,其长度为64。

    但是SideTabls并不是一个被定义的数据类型,它只是一个全局静态函数,返回值是一个StripedMap类型,所以其实SideTables类型就是StripedMap类型

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

    看一下StripedMap类的数据结构

    //参数模版化,这里讨论的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(模版参数,这里就是SideTable) 64字节对齐
            T value alignas(CacheLineSize);
        };
        
        //所有PaddedT struct 类型数据被存储在一个长度为64的array数组中
        PaddedT array[StripeCount];
    
        // 该方法以void *作为key 来获取void *对应在StripedMap中的下标位置
        static unsigned int indexForPointer(const void *p) {
            uintptr_t addr = reinterpret_cast<uintptr_t>(p);
            return ((addr >> 4) ^ (addr >> 9)) % StripeCount;// % StripeCount 是为了防止下标越界
        }
    
     public:
        //获取void *对应的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锁操作,最终是调用的array[i].value的相关操作。因此,对于模版参数T类型,必须具备相关的lock操作接口。
    因此,要作为StripedMap哈希表的模版参数,对于T类型还是有所要求的(就是能够进行锁操作)。而在SideTables中,T即为SideTable类型。

    SideTable

    SideTable的定义很清晰,有三个成员:

    • spinlock_t slock : 自旋锁,用于上锁/解锁 SideTable
    • RefcountMap refcnts :以DisguisedPtr<objc_object>为key的哈希表,用来存储OC对象的引用计数(仅在未开启isa优化 或 在isa优化情况下isa_t的引用计数溢出时才会用到)。
    • weak_table_t weak_table : 存储对象弱引用指针的哈希表。是OC weak功能实现的核心数据结构。

    除了三个成员外,苹果为SideTable还写了构造和析构函数,在析构函数的源码中可以看出来,SideTable是不能被析构的。

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

    最后是一堆锁操作,用于多线程访问SideTable, 同时,也符合我们上面提到的StripedMap中关于T模版参数的lock接口定义。

    struct SideTable {
        spinlock_t slock;  // 自旋锁,防止多线程访问冲突
        RefcountMap refcnts; // 对象引用计数表
        weak_table_t weak_table; // 对象弱引用表
    
        //构造函数
        SideTable() {
            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.
        // 按锁地址顺序对两个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类型的非公平的自旋锁。所谓非公平,就是说获得锁的顺序和申请锁的顺序无关,也就是说,第一个申请锁的线程有可能会是最后一个获得到该锁,或者是刚获得锁的线程会再次立刻获得到该锁,造成饥饿等待。 同时,在OC中,_os_unfair_lock_opaque也记录了获取它的线程信息,只有获得该锁的线程才能够解开这把锁。

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

    关于自旋锁的实现,苹果并未公布,但是大体上应该是通过操作_os_unfair_lock_opaque 这个uint32_t的值,当大于0时,锁可用,当等于或小于0时,需要锁等待。

    RefcountMap refcnts

    可以看这个
    RefcountMap refcnts 用来存储OC对象的引用计数。它实质上是一个以objc_object为key的哈希表,其vaule就是OC对象的引用计数。同时,当OC对象的引用计数变为0时,会自动将相关的信息从哈希表中剔除。
    RefcountMap的本质是一个DenseMap类,本文中的调用模板的三个类型参数DisguisedPtr<objc_object>size_ttrue 分别表示DenseMaphash key类型,value类型,是否允许引用计数为0的节点被使用。

    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
      默认值是false, 但RefcountMap指定其初始化为true。 这个成员标记是否可以使用值为 0 (引用计数为 1) 的桶。 因为空桶存的初始值就是 0,所以值为 0 的桶和空桶没什么区别。如果允许使用值为 0 的桶, 查找桶时如果没有找到对象对应的桶,也没有找到墓碑桶,就会优先使用值为 0 的桶。
    • Buckets
      指针管理一段连续内存空间,也就是数组,数组成员是BucketT类型的对象,我们这里将BucketT对象称为桶(实际上这个数组才应该叫桶,苹果把数组中的元素称为桶应该是为了形象一些,而不是哈希桶中的桶的意思)。桶数组在申请空间后,会进行初始化,在所有位置上都放上空桶(桶的 key 为EmptyKey时是空桶),之后对引用计数的操作,都要依赖于桶。
      桶的数据类型实际上是std::pair,类似于swift中的元祖类型,就是将对象地址和对象的引用计数(这里的引用计数类似于 isa,也是使用其中的几个 bit 来保存引用计数,留出几个 bit 来做其它标记位)组合成一个数据类型。
    • NumEntries
      记录数组中已使用的非空的桶的个数。
    • NumTombstones
      Tombstone直译为墓碑, 当一个对象的引用计数为0,要从桶中取出时,其所处的位置会被标记为TombstoneNumTombstones就是数组中的墓碑的个数。后面会介绍到墓碑的作用。
    • NumBuckets
      桶的数量,因为数组中始终都充满桶,所以可以理解为数组大小。

    RefcountMap 的工作逻辑

    1. 通过计算对象地址的哈希值, 来从SideTables中获取对应的 SideTable. 哈希值重复的对象的引用计数存储在同一个 SideTable 里.
    2. SideTable 使用 find() 方法和重载 [] 运算符的方式, 通过对象地址来确定对象对应的桶. 最终执行到的查找算法是 LookupBucketFor().
    3. 查找算法会先对桶的个数进行判断, 如果桶数为 0 则 return false 回上一级调用插入方法. 如果查找算法找到空桶或者墓碑桶, 同样 return false 回上一级调用插入算法, 不过会先记录下找到的桶. 如果找到了对象对应的桶, 只需要对其引用计数+ 1 或者- 1. 如果引用计数为 0 需要销毁对象, 就将这个桶中的 key 设置为 TombstoneKey
    4. 插入算法会先查看可用量, 如果哈希表的可用量(墓碑桶+空桶的数量)小于 1/4, 则需要为表重新开辟更大的空间, 如果表中的空桶位置少于 1/8 (说明墓碑桶过多), 则需要清理表中的墓碑. 以上两种情况下哈希查找算法会很难查找正确位置, 甚至可能会产生死循环, 所以要先处理表, 处理表之后还会重新分配所有桶的位置, 之后重新查找当前对象的可用位置并插入. 如果没有发生以上两种情况, 就直接把新的对象的引用计数放入调用者提供的桶里.
    bool LookupBucketFor(const LookupKeyT &Val,
                           const BucketT *&FoundBucket) const {
        ...
        if (NumBuckets == 0) { //桶数是0
          FoundBucket = 0;
          return false; //返回 false 回上层调用添加函数
        }
        ...
        unsigned BucketNo = getHashValue(Val) & (NumBuckets-1); //将哈希值与数组最大下标按位与
        unsigned ProbeAmt = 1; //哈希值重复的对象需要靠它来重新寻找位置
        while (1) {
          const BucketT *ThisBucket = BucketsPtr + BucketNo; //头指针 + 下标, 类似于数组取值
          //找到的桶中的 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; //找到空占位符, 则表明表中没有已经插入了该对象的桶
          }
          //如果找到了墓碑
          if (KeyInfoT::isEqual(ThisBucket->first, TombstoneKey) && !FoundTombstone)
            FoundTombstone = ThisBucket;  // 记录下墓碑
          //这里涉及到最初定义 typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap, 传入的第三个参数 true
          //这个参数代表是否可以清除 0 值, 也就是说这个参数为 true 并且没有墓碑的时候, 会记录下找到的 value 为 0 的桶
          if (ZeroValuesArePurgeable  && 
              ThisBucket->second == 0  &&  !FoundTombstone) 
            FoundTombstone = ThisBucket;
    
          //用于计数的 ProbeAmt 如果大于了数组容量, 就会抛出异常
          if (ProbeAmt > NumBuckets) {
              _objc_fatal("...");
          }
          BucketNo += ProbeAmt++; //本次哈希计算得出的下表不符合, 则利用 ProbeAmt 寻找下一个下标
          BucketNo&= (NumBuckets-1); //得到新的数字和数组下标最大值按位与
        }
      }
    

    weak_table_t weak_table

    weak_table_t weak_table 用来存储OC对象弱引用的相关信息。我们知道,SideTables一共只有64个节点,而在我们的APP中,一般都会不只有64个对象,因此,多个对象一定会重用同一个SideTable节点,也就是说,一个weak_table会存储多个对象的弱引用信息。因此在一个SideTable中,又会通过weak_table作为哈希表再次分散存储每一个对象的弱引用信息。
    weak_table_t是一个哈希表的结构, 根据对象的地址计算哈希值, 哈希值相同的对象按照下标 +1 的形式向后查找可用位置, 是典型的闭散列算法. 最大哈希偏移值即是所有对象中计算出的哈希值和实际插入位置的最大偏移量, 在查找时可以作为循环的上限.

    /**
     * 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数组,用来存储弱引用对象的相关信息weak_entry_t
        size_t    num_entries;  // hash数组中的元素个数
        uintptr_t mask;  // hash数组长度-1,会参与hash计算。(注意,这里是hash数组的长度,而不是元素个数。比如,数组长度可能是64,而元素个数仅存了2个)
        uintptr_t max_hash_displacement; // 可能会发生的hash冲突的最大次数
    };
    

    通过对象的地址,可以在weak_table_t中找到对应的 weak_entry_tweak_entry_t中保存了所有指向这个对象的弱引用信息。
    寻找的过程主要在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; // 根据ref的地址获取hash值,然后和mask按位与运算,保证不越界
        size_t index = begin;
        size_t hash_displacement = 0; // hash冲突位移次数
        while (weak_table->weak_entries[index].referent != referent) { // 循环判断weak_entry_t中的referent 是否与要找的referent相同
            index = (index+1) & weak_table->mask; // 下标+1并和mask按位与运算,保证数组不越界
            if (index == begin) bad_weak_table(weak_table->weak_entries); // 回到初始位置还没找到对应的referent,说明weak_table有问题,抛出异常
            hash_displacement++; // 位移次数+1
            if (hash_displacement > weak_table->max_hash_displacement) {
                //位移超过hash冲突最大次数,说明没找到对应的weak_entry_t,返回空
                return nil;
            }
        }
        return &weak_table->weak_entries[index];
    }
    
    

    weak_entry_t中使用了一个共用体, 当指向这个对象的weak指针不超过 4 个, 则直接使用数组inline_referrers,省去了哈希操作的步骤,如果weak指针个数超过了 4 个,就要使用第一个结构体中的哈希表。第一个结构体的结构和weak_table_t很像,同样也是一个哈希表,其存储的元素是weak_referrer_t,实质上是弱引用该对象的指针的指针,即objc_object **new_referrer, 通过操作指针的指针,就可以使得weak引用的指针在对象析构后,指向nil

    struct weak_entry_t {
        DisguisedPtr<objc_object> referent; // 被引用的对象
        
        // 引用该对象的弱引用共用体。
        // 引用个数小于等于4,用inline_referrers数组。
        // 引用个数大于4,用哈希数组weak_referrer_t *referrers
        union {
            struct {
                weak_referrer_t *referrers;       // 弱引用该对象的指针地址的哈希数组
                uintptr_t        out_of_line_ness : 2;   // 是否使用动态哈希数组标记位
                uintptr_t        num_refs : PTR_MINUS_2; // 哈希数组中的元素个数
                uintptr_t        mask;  // 哈希数组长度-1,会参与hash计算。(和weak_table_t的mask一样)。
                uintptr_t        max_hash_displacement;  // 可能会发生的hash冲突的最大次数
            };
            struct {
                // out_of_line_ness field is low bits of inline_referrers[1]
                // 弱引用数量不超过4个时存放弱引用指针的数组
                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;
            }
        }
    };
    

    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标记,是否已经使用哈希数组
            //如果还没使用就直接循环数组,找到空位置,把弱引用指针添加进去
            // 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;
                }
            }
            
            //如果数组中已经满了, 就要使用动态哈希数组referrers了
            //从这里开始, 这一段是把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];
            }
            //配置weak_entry_t的参数
            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());
        //根据哈希数组的规则,使用量超过填装因子(一般0.7-0.8),就要扩容
        if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) { // 数组使用量超过3/4
            return grow_refs_and_insert(entry, new_referrer); //需要扩展数组并配置entry信息
        }
        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++; // 便宜次数+1
            index = (index+1) & entry->mask; // 下标值+1,并与mask安位与运算
            if (index == begin) bad_weak_table(entry); //如果找了一圈没找到空位置,说明这个entry有问题,抛出异常
        }
        if (hash_displacement > entry->max_hash_displacement) { //判断位移数是否大于原有的哈希冲突次数,如果超过就把新的偏移数重新赋值给哈希冲突数
            entry->max_hash_displacement = hash_displacement;
        }
        // 把弱引用指针添加到referrers数组
        weak_referrer_t &ref = entry->referrers[index];
        ref = new_referrer;
        // 引用次数+1
        entry->num_refs++;
    }
    

    接着是删除功能--remove_referrer(),基本上也和增加功能没什么区别:

    static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
    {
        if (! entry->out_of_line()) { //判断out_of_line_ness标记,是否已经使用哈希数组
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                // 循环判断,找到对应的ref,将其置空
                if (entry->inline_referrers[i] == old_referrer) {
                    entry->inline_referrers[i] = nil;
                    return;
                }
            }
            //如果循环一遍还没找到对应的弱引用,说明出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数组的情况
        
        size_t begin = w_hash_pointer(old_referrer) & (entry->mask); // 根据old_referrer 找到初始下标
        size_t index = begin;
        size_t hash_displacement = 0; // 哈希冲突位移次数
        while (entry->referrers[index] != old_referrer) {
            index = (index+1) & entry->mask; // 位移次数和mask按位与运算,保证不越界
            if (index == begin) bad_weak_table(entry); //循环一圈未找到,说明有bug
            hash_displacement++; // 位移次数+1
            if (hash_displacement > entry->max_hash_displacement) {
                // 如果位移次数超过了entry标记的最大冲突次数,说明有问题,抛出异常
                _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;
        // 引用次数-1
        entry->num_refs--;
    }
    

    总结

    调用storeWeak()函数 --> 获取oldTablenewTable两个引用计数表 --> 调用weak_unregister_no_lock()函数删除掉oldObj的弱引用 --> 先调用weak_entry_for_referent()找到弱引用信息表 --> 在调用remove_referrer()删除弱引用信息 --> 调用weak_register_no_lock()把新的弱引用信息添加到newObj的弱引用表 --> 调用weak_entry_for_referent()找到newObj的弱引用信息表 --> 调用append_referrer()location添加到弱引用表中 --> 把newObj赋值给location

    相关文章

      网友评论

          本文标题:weak的实现原理

          本文链接:https://www.haomeiwen.com/subject/rcljwctx.html