iOS开发之runtime(3):浅析NSObject对象的isa_t

logo

本系列博客是本人的源码阅读笔记,如果有 iOS 开发者在看 runtime 的,欢迎大家多多交流。为了方便讨论,本人新建了一个微信群(iOS技术讨论群),想要加入的,请添加本人微信:zhujinhui207407,【加我前请备注:ios 】,本人博客http://www.kyson.cn 也在不停的更新中,欢迎一起讨论

本文完整版详见笔者小专栏:https://xiaozhuanlan.com/runtime

分析

上一篇文章中我们说到isa其实是个联合体,那什么是联合体,笔者再带大家温习一下:

联合体
在进行某些算法的C语言编程的时候,需要使几种不同类型的变量存放到同一段内存单元中。也就是使用覆盖技术,几个变量互相覆盖。这种几个不同的变量共同占用一段内存的结构,在C语言中,被称作“共用体”类型结构,简称共用体,也叫联合体。

联合体和结构体
“联合”与“结构”有一些相似之处。但两者有本质上的不同。在结构中各成员有各自的内存空间,一个结构体变量的总长度大于等于各成员长度之和。而在“联合”中,各成员共享一段内存空间,一个联合变量的长度等于各成员中最长的长度。应该说明的是,这里所谓的共享不是指把多个成员同时装入一个联合变量内,而是指该联合变量可被赋予任一成员值,但每次只能赋一种值,赋入新值则冲去旧值。如下面介绍的“单位”变量,如定义为一个可装入“班级”或“教研室”的联合后,就允许赋予整型值(班级)或字符型(教研室)。要么赋予整型值,要么赋予字符型,不能把两者同时赋予它。联合类型的定义和联合变量的说明:一个联合类型必须经过定义之后,才能把变量说明为该联合类型。

演示代码如下:

#include  
using namespace std;  
  
union U1  
{  
    int n;  
    char s[11];  
    double d;  
};  
  
union U2  
{  
    int n;  
    char s[5];  
    double d;  
};  
  
int main()  
{  
    U1 u1;  
    U2 u2;  
    cout<

上述代码中:
对于U1联合体,s占11字节,n占4字节,d占8字节,因此其至少需1字节的空间。然而其实际大小并不是11,用运算符sizeof测试其大小为16。这是因为这里存在字节对齐的问题,11既不能被4整除,也不能被8整除。因此补充字节到16,这样就符合所有成员的自身对齐了。从这里可以看出联合体所占的空间不仅取决于最宽成员,还跟所有成员有关系,即其大小必须满足两个条件:

  • (1)大小足够容纳最宽的成员;
  • (2)大小能被其包含的所有基本数据类型的大小所整除。

对于U2联合体,同理知道,用运算符sizeof测试其大小为8。

运行后的结果如下:

16  8
u1各数据地址:
0x7ffeefbff608  0x7ffeefbff608  0x7ffeefbff608  0x7ffeefbff608
u1各数据地址:
0x7ffeefbff5d0  0x7ffeefbff5d0  0x7ffeefbff5d0  0x7ffeefbff5d0
Program ended with exit code: 0

上篇文章中,我们比对两个类是否相等最终判断了

isa.bits & ISA_MASK

的值是否相等。那为什么判断这两个的值是否相等即可呢,这即是本文讨论的话题。
首先浏览一下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__
#   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)
    };

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
    struct {
        uintptr_t nonpointer        : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 8;
#       define RC_ONE   (1ULL<<56)
#       define RC_HALF  (1ULL<<7)
    };

# else
#   error unknown architecture for packed isa
# endif

// SUPPORT_PACKED_ISA
#endif


#if SUPPORT_INDEXED_ISA

# if  __ARM_ARCH_7K__ >= 2

#   define ISA_INDEX_IS_NPI      1
#   define ISA_INDEX_MASK        0x0001FFFC
#   define ISA_INDEX_SHIFT       2
#   define ISA_INDEX_BITS        15
#   define ISA_INDEX_COUNT       (1 << ISA_INDEX_BITS)
#   define ISA_INDEX_MAGIC_MASK  0x001E0001
#   define ISA_INDEX_MAGIC_VALUE 0x001C0001
    struct {
        uintptr_t nonpointer        : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t indexcls          : 15;
        uintptr_t magic             : 4;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 7;
#       define RC_ONE   (1ULL<<25)
#       define RC_HALF  (1ULL<<6)
    };

# else
#   error unknown architecture for indexed isa
# endif

// SUPPORT_INDEXED_ISA
#endif

};

去掉注释,以及其他平台的兼容性代码(主要是x86_64相关的代码)后简化如下:

union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;

#   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的时候,相信大家还是比较难以理解:

  • 结构体后的冒号是什么意思
  • # define ISA_MASK 0x0000000ffffffff8ULL定义的数字的含义

1.冒号是位域

位域
位域是指信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几 个不同的区域, 并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。

首先大家需要知道,不管X86还是arm的处理器都是64位的。16位操作系统中,int 占16位;在32位操作系统中,int 占32位。但是后来人们已经习惯了 int 占32位,因此在64位操作系统中,int 仍为32位。64位整型用 long long 或者64位即8个字节,即64位。
在文章结构体对齐(图解)与位域 中大家可以了解到位域对于结构体的大小起到一定的作用。

因此我们不难理解:isa_t中的bits占用了64位的数据。
上一篇文章中的

isa.bits & ISA_MASK

#   define ISA_MASK        0x0000000ffffffff8ULL

我们来看一下,这个0x0000000ffffffff8ULL换算成二进制


本文完整版详见笔者小专栏:https://xiaozhuanlan.com/runtime


有31位都是1。 对isa.bitsISA_MASK进行与操作,会发生什么“化学反应”呢?

位操作符
位操作符包括:&(按位与)、|(按位或)、^(按位异或)。这三个操作符非常简单,需要注意的是,这三个操作符操作的必须是整数。

这里以&为例:
当&两边是bool类型的值时,该运算符作为逻辑运算符。作用如下:
当运算符两边的表达式的结果都为true时,整个运算结果才为true,否则,只要有一方为false,则结果为false。
当&两边不是bool类型的时候,该运算符作为位运算符,将两边的值作为二进制展开,依次对每一位进行 按位与。作用如下:

11100101 & 01011010 = 01000000

经过以上分析,我们不难得出:
上图中可以看出ISA_MASK的值转化为二进制中有33位都为1,上面的例子可以看出,按位与的作用是可以取出这33位中的值。那么此时很明显了,同ISA_MASK进行按位与运算即可以取出Class的值。

写到这里,我们再回头看看isa_t的源码,不难发现,这33位对应的是结构体的shiftcls的位域。其他的位域在最后也一并做个预习吧:

struct {
    // 1代表优化后的使用位域存储更多的信息。
    uintptr_t nonpointer        : 1; 

   // 是否有设置过关联对象
    uintptr_t has_assoc         : 1;

    // 是否有C++析构函数
    uintptr_t has_cxx_dtor      : 1;

    // 存储着Class对象的内存地址信息
    uintptr_t shiftcls          : 33; 

    // 用于在调试时分辨对象是否未完成初始化
    uintptr_t magic             : 6;

    // 是否有被弱引用指向过。
    uintptr_t weakly_referenced : 1;

    // 对象是否正在释放
    uintptr_t deallocating      : 1;

    // 引用计数器是否过大无法存储在isa中
    // 如果为1,那么引用计数会存储在一个叫SideTable的类的属性中
    uintptr_t has_sidetable_rc  : 1;

    // 里面存储的值是引用计数器减1
    uintptr_t extra_rc          : 19;
};

总结:
本文从isa的bits出发,总结了NSObject对象的联合体isa的部分字段的含义。希望大家对isa有更深的理解。


本文完整版详见笔者小专栏:https://xiaozhuanlan.com/runtime


广告

我的首款个人开发的APP壁纸宝贝上线了,欢迎大家下载。

壁纸宝贝

你可能感兴趣的:(iOS开发之runtime(3):浅析NSObject对象的isa_t)