isa的走位和类的底层结构

671 阅读4分钟

前言

前面我们探究了alloc对象,对象是由类alloc出来的,更确切的说是由alloc开辟空间绑定类的isa。所以对象源自于类,我们这篇文章就来探寻一下类。

一、isa

前面的探索中,一直离不开isa,首先上一张官方的isa走位和继承图

isa流程图.png 这张图中包含了继承链和isa走位,接下来我们来一一验证

1、验证继承链

首先我们创建2个类LGPersonLGDirverLGPerson继承于NSObjectLGDirver继承于LGPerson,如图:

截屏2021-06-21 下午2.13.03.png

然后打印类和元类的superClass,如图:

截屏2021-06-21 下午4.30.46.png 结果

截屏2021-06-21 下午4.32.18.png 按照结果我们画个简易的继承图:

继承链.png

是不是好像在哪看过?上面的isa走位图的右边2列就是咯!只是名字没有改。

2、验证isa链

1、方法

要验证isa的走位我们需要通过isa获取到类的指针地址,通过object_getClass我们找到了

截屏2021-06-24 下午3.17.54.png 再进入getIsa()

截屏2021-06-24 下午3.25.54.png 进入ISA()

截屏2021-06-24 下午3.29.50.png 然后在return (Class)(isa.bits & ISA_MASK);isaisa.bits是一样的,可以看我的另一篇文章传送门的结尾部分有提到。 所以获取到class的方法就是 isa & ISA_MASK,我们再来看看ISA_MASK

截屏2021-06-24 下午3.40.36.png 分为arm64x86_642种,我们跑的是mac(本人电脑非M1),所以看x86_64模式,所以ISA_MASK就是0x00007ffffffffff8,方法找清楚了我们开始干。

2、验证

我们创建实例并获取其元类根元类根根元类,打印各类及其地址,如图:

截屏2021-06-24 下午11.30.35.png 跑起来,结果如下:

截屏2021-06-24 下午11.35.01.png 这个看不出来什么,需要我们分析,用前面的方法进行分析,分析结果如图:

isa分析.png 我们用实例kcisa获取到了的地址、isa获取到了元类的地址、元类isa获取到了根元类的地址、根元类isa获取到了根根元类的地址,对比根元类根根元类的地址发现是一样的,所以画个简易的isa走位图:

未命名文件-2.png 同理可得 LGPersonisa走位图和NSObjectisa走位图都是一样的,那么合并起来,如图:

未命名文件-3.png 是不是和官方的isa走位一样?这样就验证了isa的走位。

3、走位图

我们分别验证了继承链isa的走位图,接下来我们把它们放在一起来还原官方的图:

未命名文件-4.png

二、类的结构

首先从源码中截取一段的结构体: 1.jpg 这里有3个成员变量,加上继承于objc_object,如图:

截屏2021-07-01 下午11.07.02.png 所以就是4个成员变量:isasuperclasscachebits

  • isa:结构体指针8字节;
  • superclass: 结构体指针8字节;
  • cache: 未知;
  • bits:未知; 我们对未知的进行探究:

1、cache的大小

cachecache_t类型,我们进去看一下:

3.jpg 如图,在64位环境中的标注,所以cache的大小为 8 + 8 = 16

2、bits

在探究bits前我们需要了解地址偏移,就是连续的内存的变量可以通过前一个变量的首地址+大小(偏移量)得到后一个变量的首地址,不多赘述。

我们已经知道和前3个成员变量的大小了,所以bits的首地址我们已经算是知道了,留作备用。

1、源码查看

接下来,我们看一下class_data_bits_t类型,如图:

截屏2021-07-01 下午11.50.54.png 太长了,简单的处理一下,截个全图。我们发现只有一个bits的成员,除开swift相关的代码,我们发现2个不一样的东西class_rw_tclass_ro_t,打开方法看一下:

截屏2021-07-02 上午12.05.26.png class_ro_t通过class_rw_t获取的,所以我们只看class_rw_t,进入,代码太长,简单处理,如图:

截屏2021-07-02 上午12.02.54.png 从成员变量中我们看不到什么特别的东西,继续找方法,发现method_array_tproperty_array_tprotocol_array_t。 捋了一遍源码,还是没太多的头绪,接下来代码跑起来,我们进行lldb查看。

2、lldb调试

利用前面的方法和源码分析,我们用lldb来验证。创建LGPerson截屏2021-07-12 下午3.20.04.png 调用: 截屏2021-07-12 下午3.22.29.png 断点进入程序: 截屏2021-07-12 下午2.16.04.png 接下来直接开始调试,利用pISA_MASK获取到LGPerson这个类,加深印象,如图: 截屏2021-07-12 下午2.19.42.png 此时获取到LGPerson再继续查看内部:

截屏2021-07-12 下午2.25.29.png 根据前面的计算,取到LGPerson的地址0x100004718,便宜计算得到bits的地址为0x100004738,强转打印出来:

截屏2021-07-12 下午2.29.32.png 调用data()方法:

截屏2021-07-12 下午2.30.52.png

1、查看property_array_t

截屏2021-07-12 下午3.29.48.png 获取ptr:

截屏2021-07-12 下午3.37.40.png 然后开始获取所有的成员,注意count = 8,所以只有8个:

截屏2021-07-12 下午3.40.18.png 对比一下发现和我们的LGPerson是一样的。

2、查看method_array_t

截屏2021-07-12 下午3.45.06.png 有17个方法,我们获取部分,如图:

截屏2021-07-12 下午3.48.59.png 很奇怪,是空的,我们去method_t中看一下:

截屏2021-07-12 下午3.50.55.png 发现方法存在big里,再次尝试:

截屏2021-07-12 下午3.54.39.png

截屏2021-07-12 下午3.54.49.png 随便取了一些,发现除了第4位是我们自定义的saySomething,其他的都是setget方法,8个属性的setget方法共16,加上saySomething所以是17个。

3、查看protocol_array_t

截屏2021-07-12 下午4.07.10.png 由于没有协议,所以查看不到。

3、简单总结

中含有4个成员变量:isasuperclasscachebits,其中bits里存储了属性方法协议等。