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

最近在看源码的过程中,问了自己几个问题。
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& SideTables() {
    return *reinterpret_cast*>(SideTableBuf);
}
typedef objc::DenseMap,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
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template
    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 > 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』引用计数到底是存放在哪里?)