美文网首页开发技术文章
『ios』引用计数到底是存放在哪里?

『ios』引用计数到底是存放在哪里?

作者: butterflyer | 来源:发表于2021-07-27 18:53 被阅读0次

    最近在看源码的过程中,问了自己几个问题。
    1.引用计数存在哪!?
    2.isa指针内部结构是什么?
    3.retainCount内部实现是什么样?
    4.dealloc的内部实现是什么样的?
    5.sideTables为什么要是个数组。

    isa我们平时肯定知道是什么?但是内部结构呢?
    通过查资料
    在 arm64 架构之前,isa就是一个普通的指针,直接指向objc_class,存储着Class、Meta-Class对象的内存地址。instance对象的isa指向class对象,class对象的isa指向meta-class对象;

    // objc.h
    struct objc_object {
        Class isa;  // 在 arm64 架构之前
    };
    

    从 arm64 架构开始,对isa进行了优化,用nonpointer表示,变成了一个共用体(union)结构,还使用位域来存储更多的信息。将 64 位的内存数据分开来存储着很多的东西,其中的 33 位才是拿来存储class、meta-class对象的内存地址信息。要通过位运算将isa的值& ISA_MASK掩码,才能得到class、meta-class对象的内存地址。

    // objc-private.h
    struct objc_object {
    private:
        isa_t isa;  // 在 arm64 架构开始
    };
    
    union isa_t 
    {
        isa_t() { }
        isa_t(uintptr_t value) : bits(value) { }
    
        Class cls;
        uintptr_t bits;
    
    #if SUPPORT_PACKED_ISA
    
        // extra_rc must be the MSB-most field (so it matches carry/overflow flags)
        // nonpointer must be the LSB (fixme or get rid of it)
        // shiftcls must occupy the same bits that a real class pointer would
        // bits + RC_ONE is equivalent to extra_rc + 1
        // RC_HALF is the high bit of extra_rc (i.e. half of its range)
    
        // future expansion:
        // uintptr_t fast_rr : 1;     // no r/r overrides
        // uintptr_t lock : 2;        // lock for atomic property, @synch
        // uintptr_t extraBytes : 1;  // allocated with extra bytes
    
    # if __arm64__  // 在 __arm64__ 架构下
    #   define ISA_MASK        0x0000000ffffffff8ULL  // 用来取出 Class、Meta-Class 对象的内存地址
    #   define ISA_MAGIC_MASK  0x000003f000000001ULL
    #   define ISA_MAGIC_VALUE 0x000001a000000001ULL
        struct {
            uintptr_t nonpointer        : 1;  // 0:代表普通的指针,存储着 Class、Meta-Class 对象的内存地址
                                              // 1:代表优化过,使用位域存储更多的信息
            uintptr_t has_assoc         : 1;  // 是否有设置过关联对象,如果没有,释放时会更快
            uintptr_t has_cxx_dtor      : 1;  // 是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快
            uintptr_t shiftcls          : 33; // 存储着 Class、Meta-Class 对象的内存地址信息
            uintptr_t magic             : 6;  // 用于在调试时分辨对象是否未完成初始化
            uintptr_t weakly_referenced : 1;  // 是否有被弱引用指向过,如果没有,释放时会更快
            uintptr_t deallocating      : 1;  // 对象是否正在释放
            uintptr_t has_sidetable_rc  : 1;  // 如果为1,代表引用计数过大无法存储在 isa 中,那么超出的引用计数会存储在一个叫 SideTable 结构体的 RefCountMap(引用计数表)散列表中
            uintptr_t extra_rc          : 19; // 里面存储的值是对象本身之外的引用计数的数量,retainCount - 1
    #       define RC_ONE   (1ULL<<45)
    #       define RC_HALF  (1ULL<<18)
        };
    ......  // 在 __x86_64__ 架构下
    };
    

    请仔细看nonpointer,has_assoc,has_cxx_dtor,shiftcls,magic,weakly_referenced,deallocating,has_sidetable_rc,extra_rc后面备注。
    后面要说的这句话才是重点。

    nonpointer也就是之前说过的TaggedPointer技术
    如果isa非nonpointer,即 arm64 架构之前的isa指针。由于它只是一个普通的指针,存储着Class、Meta-Class对象的内存地址,所以它本身不能存储引用计数,所以以前对象的引用计数都存储在一个叫SideTable结构体的RefCountMap(引用计数表)散列表中。
    如果isa是nonpointer,则它本身可以存储一些引用计数。从以上union isa_t的定义中我们可以得知,isa_t中存储了两个引用计数相关的东西:extra_rc和has_sidetable_rc。
    
    extra_rc:里面存储的值是对象本身之外的引用计数的数量,这 19 位如果不够存储,has_sidetable_rc的值就会变为 1;
    has_sidetable_rc:如果为 1,代表引用计数过大无法存储在isa中,那么超出的引用计数会存储SideTable的RefCountMap中。
    
    所以,如果isa是nonpointer,则对象的引用计数存储在它的isa_t的extra_rc中以及SideTable的RefCountMap中。
    
    

    可以看下retainCount的源码流程

    uintptr_t
    _objc_rootRetainCount(id obj)
    {
        assert(obj);
    
        return obj->rootRetainCount();
    }
    inline uintptr_t 
    objc_object::rootRetainCount()
    {
        if (isTaggedPointer()) return (uintptr_t)this;//如果是采用isTaggedPointer直接返回this本身
    
        sidetable_lock();
        isa_t bits = LoadExclusive(&isa.bits);//取出isa_t
        ClearExclusive(&isa.bits); 
        if (bits.nonpointer) { //如果是优化的指针
            uintptr_t rc = 1 + bits.extra_rc;
            if (bits.has_sidetable_rc) {//如果引用计数过大无法存贮在isa中
                rc += sidetable_getExtraRC_nolock();//去sidetable中去拿取计数
            }
            sidetable_unlock();
            return rc;
        }
        sidetable_unlock();
        return sidetable_retainCount();
    }
    
    size_t 
    objc_object::sidetable_getExtraRC_nolock()
    {
        assert(isa.nonpointer);
        SideTable& table = SideTables()[this];
        RefcountMap::iterator it = table.refcnts.find(this);
        if (it == table.refcnts.end()) return 0;
        else return it->second >> SIDE_TABLE_RC_SHIFT;
    }
    //没有优化过的isa去sidetable中拿计数
    uintptr_t
    objc_object::sidetable_retainCount()
    {
        SideTable& table = SideTables()[this];//根据地址拿到SideTable
    
        size_t refcnt_result = 1;
        
        table.lock();
        RefcountMap::iterator it = table.refcnts.find(this);//从SideTable中根据地址拿取RefcountMap
        if (it != table.refcnts.end()) {
            // this is valid for SIDE_TABLE_RC_PINNED too
            refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
        }
        table.unlock();
        return refcnt_result;//返回RefcountMap中的计数
    }
    

    通过上面我们可以看到如果isa中无法存储指针,那么就存储在SideTable中的RefcountMap中。
    从这里我们可以引出SideTable,那么他是什么呢?

    static StripedMap<SideTable>& SideTables() {
        return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
    }
    typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap;
    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.
    
        template<HaveOld, HaveNew>
        static void lockTwo(SideTable *lock1, SideTable *lock2);
        template<HaveOld, HaveNew>
        static void unlockTwo(SideTable *lock1, SideTable *lock2);
    };
    

    SideTable存储在SideTables()中,SideTables()本质也是一个散列表,可以通过对象指针来获取它对应的(引用计数表或者弱引用表)在哪一个SideTable中。
    查找对象的引用计数表需要经过两次哈希查找:
    ① 第一次根据当前对象的内存地址,经过哈希查找从SideTables()中取出它所在的SideTable;
    ② 第二次根据当前对象的内存地址,经过哈希查找从SideTable中的refcnts中取出它的引用计数表。
    那么回到文章开头的疑问,为什么不是一个SideTable而是存在SideTables中。
    通过源码中我看到了,SideTable是需要加锁解锁的。那么如果不同的对象,共用一个SideTable对象,那么则会造成效率问题了。所以需要拆分成不同的SideTable对象存放在SideTables中。
    下面是查资料的解释

    如果只有一个SideTable,那我们在内存中分配的所有对象的引用计数或者弱引用都放在这个SideTable中,那我们对对象的引用计数进行操作时,为了多线程安全就要加锁,就存在效率问题。
    系统为了解决这个问题,就引入 “分离锁” 技术方案,提高访问效率。把对象的引用计数表分拆多个部分,对每个部分分别加锁,那么当所属不同部分的对象进行引用操作的时候,在多线程下就可以并发操作。所以,使用多个SideTable组成SideTables()结构。
    
    

    来看下dealloc的流程

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

    如果做了isa优化,并且没有弱引用,没有关联对象,没有析构函数,没有存在sideTable中,则释放的更快

    id 
    object_dispose(id obj)
    {
        if (!obj) return nil;
    
        objc_destructInstance(obj);    
        free(obj);
    
        return nil;
    }
    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;
    }
    
    void object_cxxDestruct(id obj)
    {
        if (!obj) return;
        if (obj->isTaggedPointer()) return;
        object_cxxDestructFromClass(obj, obj->ISA());
    }
    
    void _object_remove_assocations(id object) {
        vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
        {
            AssociationsManager manager;
            AssociationsHashMap &associations(manager.associations());
            if (associations.size() == 0) return;
            disguised_ptr_t disguised_object = DISGUISE(object);
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i != associations.end()) {
                // copy all of the associations that need to be removed.
                ObjectAssociationMap *refs = i->second;
                for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
                    elements.push_back(j->second);
                }
                // remove the secondary table.
                delete refs;
                associations.erase(i);
            }
        }
        // the calls to releaseValue() happen outside of the lock.
        for_each(elements.begin(), elements.end(), ReleaseValue());
    }
    
    inline void 
    objc_object::clearDeallocating()
    {
        if (slowpath(!isa.nonpointer)) {
            // Slow path for raw pointer isa.
            sidetable_clearDeallocating();
        }
        else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
            // Slow path for non-pointer isa with weak refs and/or side table data.
            clearDeallocating_slow();
        }
    
        assert(!sidetable_present());
    }
    void 
    objc_object::sidetable_clearDeallocating()
    {
        SideTable& table = SideTables()[this];
    
        // clear any weak table items
        // clear extra retain count and deallocating bit
        // (fixme warn or abort if extra retain count == 0 ?)
        table.lock();
        RefcountMap::iterator it = table.refcnts.find(this);
        if (it != table.refcnts.end()) {
            if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
                weak_clear_no_lock(&table.weak_table, (id)this);
            }
            table.refcnts.erase(it);
        }
        table.unlock();
    }
    
    objc_object::clearDeallocating_slow()
    {
        assert(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));
    
        SideTable& table = SideTables()[this];
        table.lock();
        if (isa.weakly_referenced) {
            weak_clear_no_lock(&table.weak_table, (id)this);
        }
        if (isa.has_sidetable_rc) {
            table.refcnts.erase(this);
        }
        table.unlock();
    }
    

    相关文章

      网友评论

        本文标题:『ios』引用计数到底是存放在哪里?

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