聊聊 isa 和引用计数

cubegao 2018-05-03 PM 680℃ 0条

大家都知道内存管理是通过引用计数管理对象的生命周期。

isa和引用计数又有什么关系呢?先留着疑问。

isa指针

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;

通过注释和代码不难发现,我们创建的一个对象或实例其实就是一个struct objc_object结构体。

这个结构体只有一个成员变量,这是一个Class类型的变量isa,也是一个结构体指针,那这个指针又指向什么呢?

面向对象中每一个对象都必须依赖一个类来创建,因此对象的isa指针就指向对象所属的类根据这个类模板能够创建出实例变量、实例方法等。

1975281-cc44a16eb3f252d3.jpg
结合这张图片,就很好理解了。类方法的实现又是如何查找并且调用的呢?这时,就需要引入元类来保证无论是类还是对象都能通过相同的机制查找方法的实现。整个过程被设计成了一个闭环。

总结一下就是,isa指针是用来维护对象和类之间的关系,并确保对象和类能够通过isa指针找到对应的方法,实例变量,属性,协议等。在 arm64 架构之前,isa就是一个普通的指针,直接指向objc_class,存储着Class、Meta-Class对象的内存地址。instance对象的isa指向class对象,class对象的isa指向meta-class对象;

引用计数

arm64之前

arm64架构之前,引用计数都是存储在sidetable这个数据结构中。

SideTables(哈希表,key对象地址,value 是 SideTable)
    |
    |---SideTable
    |       |
    |       |---slock(自旋锁)
    |       |
    |       |---refcnts(引用计数哈希表, key 是对象地址,value是引用计数)
    |       |
    |       |---weak_table(弱引用哈希表,key是对象地址,value 是 entry)
    |               |
    |               |---entry(也可以理解为是哈希表,存储一个对象的所有弱引用,key 是弱引用地址(id*), value 也是弱引用地址)
    |               |
    |               |
    |               |---entry
    |               |      .
    |               |      .
    |               |      .
    |
    |
    |---SideTable
    |       .
    |       .
    |       .

SideTables可以理解为一个全局的hash数组,里面存储了SideTable类型的数据,其长度为64。

SideTableRefCountMap里面就存储着引用计数。

其中weak_table就是weak的具体实现。weak表其实是一个hash(哈希)表,Key是对象的内存地址,Value是指向该对象的所有弱引用的指针地址数组。

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

arm64之后

苹果为了进一步提高性能,优化了isa结构体指针。

struct objc_object {
private:
    isa_t isa; 
};

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__ 架构下
};

如果isanonpointer,即 arm64 架构之前的isa指针。由于它只是一个普通的指针,存储着Class、Meta-Class对象的内存地址,所以它本身不能存储引用计数,所以以前对象的引用计数都存储在一个叫SideTable结构体的RefCountMap(引用计数表)散列表中。
如果isanonpointer,则它本身可以存储一些引用计数。从以上union isa_t的定义中我们可以得知,isa_t中存储了两个引用计数相关的东西:extra_rchas_sidetable_rc

extra_rc:里面存储的值是对象本身之外的引用计数的数量,这 19 位如果不够存储,has_sidetable_rc的值就会变为 1;
has_sidetable_rc:如果为 1,代表引用计数过大无法存储在isa中,那么超出的引用计数会存储SideTableRefCountMap中。

retain:如果isanonpointer,就将isa中的extra_rc存储的引用计数进行 +1,如果溢出,就将extra_rcRC_HALFextra_rc满值的一半)个引用计数转移到sidetable中存储。

release:如果isanonpointer,就将isa中的extra_rc存储的引用计数进行 -1。如果下溢,即extra_rc中的引用计数已经为 0,判断has_sidetable_rc是否为true即是否有使用Sidetable存储。如果有的话就申请从Sidetable中申请RC_HALF个引用计数转移到extra_rc中存储,如果不足RC_HALF就有多少申请多少,然后将Sidetable中的引用计数值减去RC_HALF(或是小于RC_HALF的实际值),将实际申请到的引用计数值 -1 后存储到extra_rc中。如果extra_rc中引用计数为 0 且has_sidetable_rcfalse或者Sidetable中的引用计数也为 0 了,那就dealloc对象。

所以,如果isanonpointer,则对象的引用计数存储在它的isa_textra_rc中以及SideTableRefCountMap中。

标签: isa

非特殊说明,本博所有文章均为博主原创。

评论啦~