美文网首页
iOS底层-类的底层原理(一)

iOS底层-类的底层原理(一)

作者: 忻凯同学 | 来源:发表于2021-06-22 01:42 被阅读0次

    前言

    通常在创建对象的时候,都会继承 NSObject去新建一个类,那么NSObject 继承谁?或者说类的底层原理是什么?下面来具体探究一下。

    本文探索过程会涉及到 对象的本质

    准备工作

    • 新建一个 Project
    • main.m 中添加一个类 ZLObject,打上断点并执行。

    案例分析

    1. 探索对象的底层
    • $ p obj:查看 obj 对象的地址。

    • $ x/4gx obj地址:查看 obj 对象的isa及内存占用。

    • $ p/x isa地址 & 掩码地址:与掩码做与运算

    • $ po 与运算地址:查看关联类

    流程如下:

    其中掩码为 __86_64__ 的掩码地址 0x00007ffffffffff8ULL,最终得到 ZLObject 的地址:0x0000000100008260

    2. 继续探索

    以上面 ZLObject0x0000000100008260 地址,再次进行 isaISA_MASK 与运算,最终得到 0x0000000100008238 的地址,还是 ZLObject

    这就比较奇怪了,为什么都是 ZLObject,内存地址却不一样?

    3. 再次探索

    再次以新的 ZLObject0x0000000100008238 地址,再次进行 isaISA_MASK 与运算,最终得到 0x00007fff92c9d0f0 的地址,是 NSObject

    对此,有两个疑问:

    • 两次的 ZLObject 是否存在的一定的联系?

    • NSObjectisa 指向了什么?

    4. 方法印证

    添加下图方法,并打印其内存情况。

    打印结果如下:

    通过上面的案例,得到的结论是:对象的 isa 是指向 ,也就是 ZLObject 的内存地址 0x0000000100008260,那么类的 isa 指向的是什么?为什么这块内存地址也是 ZLObject

    5. MachO文件分析

    有关MachO文件探索,请移步 MachO文件分析

    通过 MachOView 的分析,直接定义到 Symbol Table 下查看所有的 symbols 数据,搜索 class,得出:

    • 找到了 0x0000000100008260 的内存地址,其符号下标就是 _OBJC_CLASS_$_ZLObject,也就是 ZLObject
    • 找到了 0x0000000100008238 的内存地址,其符号下标就是 _OBJC_METACLASS_$_ZLObject,称为 ZLObject 的元类。

    结论一

    • 对象的 isa 指向

    • 类的 isa 指向 元类

    • 元类 是系统编译器生成的。

    MetaClass的本质

    上面的分析中,提出了两个疑问,其中第一个已经证实,两次的 ZLObject,一个是 ,一个是 元类。那么,NSObjectisa 指向了什么?接下来我们继续探索。

    通过查看 NSObject.class 的内存地址 0x00007fff92c9d118,发现和之前的 NSObject 地址 0x00007fff92c9d0f0 不一样,因此 0x00007fff92c9d0f0NSObject元类

    那么 NSObject元类isa 又指向了什么?

    通过运算分析,NSObject元类isa 还是指向 NSObject元类

    结论二

    • 元类isa 指向 根元类

    • 根元类isa 还是指向 根元类

    • 对于 NSObject ,它也是 根类根类isa 也是指向 根元类

    SuperClass的本质

    上面分析了 ZLObject 类和 NSObject类的 isa 指向情况,那么父类 SuperClassisa 指向情况和继承关系如何呢?

    1. NSObject SuperClass

    创建如图类,并打印其内存情况:

    打印结果如下:

    说明:

    • NSObject 的父类是 nil

    • NSObject 的元类的父类还是 NSObject

    2. ZLObject SuperClass

    首先看一下类的父类是 NSObject 的情况,打印其元类:

    打印结果如下:

    说明:ZLObject 的元类的父类是 NSObject 的元类

    3. ZLSubObject SuperClass

    创建继承于 ZLObject 的子类 ZLSubObject,并打印如图内存:

    打印情况如下:

    说明:ZLSubObject 的元类的父类是 ZLObject 的元类。

    结论三

    • NSObject 的父类是 nil,其元类的父类还是 NSObject

    • 父类的元类也有继承关系。

    最终得到两个关系图,一个是类的 isa 指向图,一个是类的继承链图。

    类的 isa 指向图 类继承链图

    内存偏移

    1. 普通指针

    打印结果:

    说明:常量10处于 常量区 ,可以被 不同 的指针引用,其引用原理为 值拷贝

    2. 数组指针

    打印结果:

    说明:

    • 使用数组 下标 取地址,和利用 指针偏移量 取值效果一样。不如上图中是 &c[0]b + 1

    • 数组的 首地址 也就是数组 第一个 元素的地址。

    • 指针 偏移量大小 和数组元素所占用 字节大小 有关,比如上图中是 int,所以打印结果地址相差 4,也称 步长

    类的内存结构

    分析源码可知,objc_class 方法实现如下:

    其内存结构图如下:

    因此如果想要得到 bits,就必须知道 superclasscache 的内存字节数,再利用内存偏移得到 bits

    由于 isa8 字节,此处不再赘述。

    1. superclass

    superclassisa 一样,也是 8 字节,因为都是 Class 结构体类型

    2. cache

    cache_t 的有效代码如下:

    cache_t 中的方法和 static 声明的字段都不是在该结构体内,所以只需要分析上面的有效代码,获取 cache_t 所占用的内存大小,即可得到 bits 的内存偏移量。

    1. 联合体之外的 explicit_atomic<uintptr_t>的大小:
    explicit_atomic 为泛型指针,所以其内存大小由 <uintptr_t> 决定的,也就是 uintptr_t 的大小,为 8 字节。也可以使用 sizeof(uintptr_t) 查看其字节大小。

    2. 联合体的大小:

    说明:

    • mask_tuint32_t 类型,所以为 4uint16_t 类型为 2

    • <preopt_cache_t *> 为结构体指针,所以为 8

    • 联合体内部 内存共用 ,且 互斥 的特性,所以总大小为 8

    结论

    cache 所占字节为 16

    bits 的内存偏移量为 isa + superclass + cache,总大小为 32

    类的底层数据获取

    1. 获取 bits 数据:

    上图可得类的首地址为:0x1000081e8,那么以类的首地址偏移 32 字节,就是 bits

    此时 bits 的数据存储在 $4 中。

    在上面分析 objc_class 的结构时,可以对 data() 进行存取数据。

    因此,可以通过 $4->data() 方法获取 class_rw_t 内存地址:

    2. 获取 class_rw_t 数据:

    这样 class_rw_t 的数据就可以拿到了,但是并不知道所需的属性和方法具体存在哪里。

    3. 分析 class_rw_t 结构体:

    通过 properties()methods() 获取类的属性和方法。

    4. 获取 property_array_tlist

    5. 获取 listptr

    6. 获取 property_list_t 的数据 :

    7. 使用 C++数组 get() 方法,获取类的属性 :

    8. 同理获取类的 methods()

    最后一步的 get(0) 没有拿到数据,因此获取方法和属性不一样。

    9. 分析 method_t

    10. 调用 big() 函数。

    类的底层探索流程图

    相关文章

      网友评论

          本文标题:iOS底层-类的底层原理(一)

          本文链接:https://www.haomeiwen.com/subject/hdwhyltx.html