类对象的底层探索(上)

168 阅读4分钟

我们在上篇文章了解到,实例对象中存储了isa指针以及成员变量的值,并且isa指针指向了类对象的所在。我们将在接下来的文章中对类对象的底层进行探索。

我们对以下内容进行输出发现其输出一模一样,也就是说这三种方式可以用来获取类对象。 截屏2022-04-23 下午2.17.17.png

实例对象,类对象,元类对象--isa指针走向以及继承关系

iShot2022-04-23_15.03.47.png

从上面的验证我们可以得出以下isa指针的走向图: 截屏2022-04-23 下午3.32.55.png

那么根元类的isa指针指向了谁?我们接着探索: 截屏2022-04-23 下午3.36.26.png

所以我们得出的最后的isa指针完整走向图为:

截屏2022-04-23 下午3.40.49.png

那么元类对象的父类是谁呢?我们对以下内容进行输出,就会得出答案: 截屏2022-04-23 下午4.39.00.png Person类继承自NSObject类,由图中的输出可得到结果Person元类对象父类NSObject元类对象xx的元类对象父类xx的类对象的父类的元类对象。也可以得出根元类的父类是根类

我们将继承链isa指针链进行整合: 截屏2022-04-23 下午5.02.02.png

类对象中有什么?

我们知道实例对象的底层是结构体objc_object,类对象的底层是结构体objc_class

struct objc_object {
    isa_t isa // 8
}

struct objc_class: objc_object {
    Class superclass; // 8
    cache_t cache; // 16
    class_data_bits_t bits
}

typedef struct objc_object *id;
typedef struct objc_class *Class;

既然我们拿到了类对象的结构,根据我们目前已知的,这个结构体里面有个isa指针指向了元类对象以及一个指向父类的指针,再一个cache,这个后面我们会讲到,所以我们猜测这个class_data_bits_t类型的bits里面是我们需要的东西。首先我们来看看什么是内存平移。

内存平移

截屏2022-04-23 下午7.49.28.png 如图中所示,p是个指针,指向了整形数组的首地址cd+1则表示指针平移int类型大小的字节,即就是平移4个字节,也就是数组第一个元素的地址,以此类推。

好了,知道了什么是内存平移,我们来对这个bits进行探究。我们定义一个Person类, 截屏2022-04-23 下午10.33.14.png 截屏2022-04-23 下午10.33.21.png 运行程序,并且在main函数中打上断点,先输出Person类对象的地址分布,参考源码中objc_class的结构(全局搜索struct objc_class),要拿到bits,需要内存平移32位,也就是说0x1000081b8就是我们要操作的地址。我们将该地址进行强转为class_data_bits_t*得到一个地址,我们通过*对地址进行取值,得到的结果对我们并没有什么参考意义,怎么办呢? 截屏2022-04-23 下午9.45.56.png

不过结果返回的class_data_bits_t类型我们可以试着对其进行探索,跳转到其定义处:

截屏2022-04-23 下午9.48.22.png

发现其有个叫做class_rw_t* data()的方法,我们试着对其进行调用并得到的地址进行取值: 截屏2022-04-23 下午9.55.42.png

好像没什么对我们有用的信息,这时候我们就跳转到class_rw_t类型的定义处,发现里面有很多方法,我们发现了几个很熟悉的方法:methods,properties,protocols等等。我们试着对其进行调用,里面有个list,我们继续对其进行解析,得到一个method_list_t类型的地址,并对其取值。 截屏2022-04-23 下午10.01.17.png 截屏2022-04-23 下午10.08.40.png 上面的输出信息里有个count,我们猜测是实例方法的个数,我们定义的类中有两个属性,settergetter方法加起来有4个,再加上定义的一个instanceMethod方法,总共有5个,可是这个count却有6个。别急,我们接着往下看。

我们查看method_list_t的定义,没找到有用的信息,去entsize_list_tt里面找: 截屏2022-04-23 下午10.11.59.png

截屏2022-04-23 下午10.13.32.png

这个get方法应该就是获取method的方法,给其传下标即可。我们可以试下,发现其输出结果并不是我们预想的那样。

截屏2022-04-23 下午10.20.07.png

那我们接着对method_t类型进行探索,跳转到其定义处,发现其有一个getDescription方法: 截屏2022-04-23 下午10.22.57.png 我们试着对其进行调用并对所得地址进行取值: 截屏2022-04-23 下午10.24.43.png

好耶,我们成功拿到了方法名以及参数列表。如法炮制,对剩下的五个方法进行打印: 截屏2022-04-23 下午10.28.39.png 原来多出的那个方法是这个叫.cxx_destruct的方法,猜测是反初始化方法。

我们也可以通过上面的探索的方式获取属性列表。

所以类对象中存储了实例方法列表,属性列表以及协议方法列表以及cache和指向元类的指针以及指向父类的指针