iOS---关于isa

1,724 阅读6分钟

ARM64位架构之前,isa是一个指针,指向class/meta-class对象的地址 ARM64位架构开始(也就是13年5s面世),isa是一个联合体/共用体(union),这是苹果对isa的优化,结合位域的概念以及位运算的方式来存储更多类相关信息,简单来说就是isa指针通过一个叫ISA_MASK的值进行二进制&运算,得到真实的class/meta-class对象的真实地址

先看一下优化前后的源码变化,在objc4-723版本的源码中可以看到以下情况:

objc.h文件中

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

可以看到,64位架构之前isa是直接指向objc_class

objc-private.h中

struct objc_object {
private:
    isa_t isa;
public:
    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();
    // getIsa() allows this to be a tagged pointer object
    Class getIsa();
。
。
。
。

64位架构开始,不再是一个普通的isa指针了,而是isa_t,这个东西进一步查看发现

union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    Class cls;
    uintptr_t bits;
#if SUPPORT_PACKED_ISA
# if __arm64__ //篇幅原因只截取部分
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    struct {
        uintptr_t nonpointer        : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 19;
#       define RC_ONE   (1ULL<<45)
#       define RC_HALF  (1ULL<<18)
    };
};

通过上述源码发现isa_t是一个union(共用体/联合体),union这个东西看上去好像跟struct差不多,只是命名不同,但是通过查阅资料发现,union的定义是它使几个不同类型的变量共占一段内存(相互覆盖),每次只有一个能使用,而struct的成员则具备独自的内存空间,也就是在使用的时候互不影响

举个例子如下 假设A类里面声明一个union

union x {
  char *name;
  int age;
  bool isRich;
}

如上定义后,系统会为union x分配8个字节的内存空间,因为指针就是占8bit,int占4bit,bool占1bit,该union的内存大小由其内部占最大字节的成员类型决定,因为后续操作是需要互相覆盖的。

再看struct的定义 假设B类里面声明一个struct

struct x {
  char *name;
  int age;
  bool isRich;
}

如上定义后,系统会为这个struct x分配16个字节的内存空间,各成员所占内存跟上述大小一致,各自占有独立对应的内存空间,不过这里由于需要内存对齐,导致结构体的内存大小不再是8bit

再看结构体中的内容,截取部分如下

struct {
        uintptr_t nonpointer        : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
        uintptr_t magic             : 6;

每个成员后面都有:x,这个:x其实就是位域的应用,简单来说就是,有的信息它所需要的内存不足以填满一个字节的空间,比如开关状态这样的信息,其实只需要一个二进制位的空间就可以存储,这样就达到节约内存的目的。(位域概念是c语言里面的一种数据结构,将一个字节中的二进位划分为多个不同的区域,每个区域具备几个位数,每个域给定一个域名,允许在程序中按域名进行操作,这样就可以把几个不同的对象用一个字节的二进制位域来表示

具体每个成员表达了什么信息,百度了一下:

nonpointer—— 0,代表普通指针,存储着class、meta-class对象的内存地址;1,代表优化过,使用位域存储更多信息

has_assoc—— 是否设置过关联对象,如果没有,施放时会速度更快

has_cxx_dtor—— 是否有C++的稀构函数,如果没有,施放时会更快

shiftcls—— 这个部分存储的是真正的Class/Meta-Class对象的内存地址信息,因此要通过 isa & ISA_MASK才能取出这里33位的值,得到对象的真正地址。

magic—— 用于在调试的时候分辨对象是否完成了初始化 weekly_referenced—— 是否被弱饮用指针指向过,如果没有,释放时会更快

extra_rc—— 里面存储的值是 引用计数 - 1

deallocating——对象是否正在被释放

has_sidtable_rc——引用计数器是否过大无法存储在isa中,若果是,这里就为1,引用计数就会被存储在一个叫SideTable的类的属性中

从解释跟命名大概意思是能看得懂的 关于释放时会更快,源码中其实是有体现

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

进一步查看clearDeallocating

inline void 
objc_object::clearDeallocating()
{
    if (slowpath(!isa.nonpointer)) {//isa为普通指针的情况下
        // Slow path for raw pointer isa.
        sidetable_clearDeallocating();
    }
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {//isa为共用体的情况
        // Slow path for non-pointer isa with weak refs and/or side table data.
        clearDeallocating_slow();
    }
    assert(!sidetable_present());
}

所以我觉得has_sidtable_rc这个应该也是会影响释放速度的吧

再看内部其它成员

前面两个isa_t(){};为构造方法,class也没问题,第四个uintptr_t bits; 这个东西类型是unsigned long类型,表达的是类的信息,上面也说了,苹果是结合了位域的概念和位运算来做的优化,也就是将结构体中的成员通过位域这种数据结构分配了各个成员的内存,达到节约内存的目的,再通过各种MASKbits进行&运算,得到想要的信息,具体想要什么信息就拿对应的MASK去运算,struct其实在实际使用过程中不会用到,只是借助于它来解释bits里面每个位所代表的含义,起到解释说明的作用

到这里就差不多把苹果的优化方案过了一遍,回过头来看点细节

union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    Class cls;
    uintptr_t bits;
#if SUPPORT_PACKED_ISA
# if __arm64__ //篇幅原因只截取部分,源代码在此判断了多个版本
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL

这几个MASK,尤其是ISA_MASK这是个16进制的表示,如果换成二进制是 1111 1111 1111 1111 1111 1111 1111 1111 1000,一共是36个有效二进制位,最后四位只可能是1000或者0000,这个可以打印任意对象的地址查看,地址必然是0x xxxxxxxx0或者0x xxxxxxxx8,转成二进制后就会很容易发现 isa&ISA_MASK取出来的到底是哪些二进制位上面的值,再到结构体中对比后可以得出这个值所代表的含义

其它细节方面肯定还有不少,但是碍于自身水平所限,感觉即便是了解了也没有太大提升,就暂时学到这个程度,个人觉得关于isa这块,抛开isa的指向问题,能知道isa的版本差异与新版中苹果对isa的优化方案应该差不多吧- -。

最后

本次记录仅为日常学习笔记,如果侥幸入了大佬法眼,还请不吝赐教,感谢

参考

objc4-723

其它