iOS底层原理之类的底层探索(上)

2,211 阅读5分钟

本文主要内容

1、isa指向关系
2、类与元类的继承关系
3、通过内存平移得到bits数据
4、获取bits里面的methodlists
5、获取bits里面的properties

一、isa指向关系

在上一遍iOS对象的底层探索(下)我们知道了实例对象的isa指针,那么实例对象的isa指针指向哪里了?首先看一下下面的代码:

image.png 通过三种方式获取LSPerson的class地址发现类对象的地址只有一个。 class类为什么称为类对象了?我们结合objc源码查看

类本质是一个叫作objc_class的结构体,objc_class继承自objc_object,而对象的本质就是叫作objc_object的结构体。
类中的isa指针也是由objc_object结构体中继承来的。所以说类也是对象。下图即为类的底层函数:

image.png 我们发现实例对象有一个isa指针,类对象继承自objc_object内部也有一个isa指针,那么他们有什么关系吗? 我们接下来分析一下实例对象的isa指针指向位置: image.png image.png 通过上面的分析,我们发现:实例对象的isa指针指向类对象,我们继续分析类对象的isa指针指向问题 image.png 通过上面的分析,我们发现:类对象isa指针指向元类,元类和类对象的名字是一样的。我们继续分析元类对象的isa指向关系

image.png

总结: 实例对象isa指向类对象,类对象isa指向元类,元类isa指向根元类,根元类isa指向根元类自己。

二、类与元类的继承关系

先看下面的代码 image.png 通过上面我们发现 LSPerson的元类的父类就是NSObject的类对象, LSTeacher的元类的父类就是LSperson的元类,也就是元类的父类就是父类的元类;NSObject的父类为null,即NSObject没有父类;根元类的父类就是NSObject类对象

如下为类与元类的继承关系图 image.png

三、通过内存平移得到bits数据

我们知道实例对象isa指向类对象,类对象本身也是struct结构体,我们接下来会研究class_data_bits_t,首先我们知道类对象里面有一个isa指针,占8字节,superClass也是占用8字节,catch_t也是一个struct结构体通过分析其内部数据成员可以发现是16个字节。 image.png

   扩展知识点
   1、内存平移:
   2、结构体内存是连续的,并且结构体的数据存储是按照顺序的,所有通过内存平移得到bits的内存地址。

按照上面的规则我们先获取类对象的地址,通过对类对象的内存平移即可获得bits的内存地址。这里需要需要借助lldb的命令 x/6gx 来获取类对象的内存地址,然后在类对象的首地址的基础上平移32个字节(ISA(8字节) + superclass(8字节) + cache(16字节)). 结果如下图: image.png

四、获取bits里面的methodlists

在上面我们找到class_data_bits_t的地址,我们继续分析,使用objc4-838.1源码调试。

image.pngclass_data_bits_t里面找到一个方法data(),我们调用该方法执行得到上图的结果此时会得到一个class_rw_t的结构体指针 image.png 执行 .data()得到class_rw_t的结构体指针,然后直接输出,发现看不懂并没有我们想要的信息,我们继续查看class_rw_t的结构体定义 发现里面有一个methods方法 image.png image.png 执行.methods()方法得到method_array_t的结构,然后我们发现了里面有一个list,继续执行发现里面有一个ptr,然后输出 image.png 我们查看上图的输出结果发现里面有一个count = 5,这个表示当前这个类里面有5个方法(不包含类方法,类方法不是存储在类对象中)。这个时候我门配合源码知道 method_list_t 继承自 entsize_list_tt. entsize_list__tt 可当成一个容器,其中代表元素的typename Element参数和容器类型的typename List参数,所以通过struct method_list_t : entsize_list_tt‹method_t, method_list_t, oxffff0003, method_t: :pointer_modifier> {} 可知,其元素类型为method_t,容器类型为method_list_t。所以可以通过某个方法获取其中的元素,这个方法为get方法 image.png image.png 调用get(0),get(1) 发现并么有有用的信息,输出的是空,是一个method_t的结构体 image.png 我们通过源码查看method_t的结构体定义找到一个getDescription方法注意在method_t里面有big和small两种,这两种代表的是大端模式、小端模式,因为电脑是arm的所以就是小端模式,所以直接使用这个方法image.png image.png 到此我们拿到了在LSPerson中定义的方法test方法。 image.png 这里我们就拿到了5个方法跟我们上面输出的count=5对应了

注意: 这里有人会有一个cxx_destruct析构方法,我这边demo并没有,是因为我的LSPerson属性并没有对象都是基本数据类型,如果属性或者成员变量有实例变量的时候才会产生这个函数

五、获取bits里面的properties

与上面获取methodlists同理,获取bits里面的propertylist。propertylist函数返回的类型位property_array_t, 容器property_array_t 中的元素为property_t 类型, property_t 结构题含有name和attribute元素。

image.png

   我们在这里讨论了属性是存在这里的,那么成员变量是存储在哪里了?下篇文章我们继续讨论。

六、总结

  1. 类对象有且只有一个。实例对象isa指向类对象,类对象isa指向元类,元类isa指向根元类,根元类isa指向他本身。
  2. 根元类的父类为NSOjbect,父类的元类就是元类的父类。
  3. objc_class 存储实例方法、属性、协议。也就是方法、属性都存在在类对象中。
  4. 知道了方法、属性存储在objc_class 的bits里面