类的底层原理探索(上)

312 阅读4分钟

Xnip2022-04-21_21-53-23.jpg

1.isa指针指向解析

1.1类对象的获取

通过以下三种方式获取类对象,输出的类对象地址相同,说明类对象有且只有一个。 image.png

1.2 类对象的本质:

从源码中可以看到Class就是objc_class结构体指针,可以看到可对象中也有ISA指针,实例对象的isa指针指向类对象,类对象的isa指针指向哪里?; image.png

1.3 类对象isa指针的探索

对实例对象的isa指针&ISA_MASK可以得到类对象,我们同样对类对象的isa&ISA_MASK可以得到一个指针,po打印指针,得到也是对象名称,我们从上面得到类对象是唯一的,那么这个指针不是类对象,这个对象是元类对象,可以得到:

Xnip2022-04-21_23-21-16.jpg Xnip2022-04-21_23-38-21.jpg

  1. 实例对象isa-->类对象,类对象的isa-->元类对象,元类对象isa->根元类,根元类isa--> 根元类;
  2. NSObject isa --> 根元类,根元类isa-->根元类;
  3. 元类和类对象名称相同;

2.元类的继承关系

有两个类LGPerson和LGTeacher,LGTeacher继承LGPerson,LGPerson继承NSObject: image.png

  1. 根元类继承NSObject;
  2. NSObject没有父类;
  3. 元类继承父类的元类;

3. 内存平移

举例,如下图: image.png c是数组的首指针,d也指向输出的首地址;d+1是内存平移,平移了4个字节,因为数组是int类型,可以用d+1指向数组的下一个地址,从而输出该值;输出如下: image.png

4.实例方法,属性的存储位置解析:

4.1 class_data_bits_t

类对象中class_data_bits_t bits前面有isa占8个字节,Class superclass占8个字节、cache_t cache 占16个字节,可以根据类对象的首地址,根据内存平移32个字节得到bits的首地址:

4.2 实例方法存储位置解析步骤

4.2.1 获取class_data_bits_t

Xnip2022-04-22_19-38-04.jpg 可以看出输出的bits,不能看得到有什么有用的内存,可以通过分析class_data_bits_t中源码找到:

class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
4.2.2获取class_rw_t

class_rw_t 中存储了实例方法,属性,协议等,调用data方法得到class_rw_t *,然后输出内容,可以看出,只能从源码寻找class_rw_t存储的内从;

p $3.data()
(class_rw_t *) $4 = 0x00000001006055e0

从源码中可以看到class_rw_t存储了实例方发,属性协议如下 image.png

4.2.3调用methods()

image.png 可以得到method_array_t数组,取list值中prt如下: image.png

4.2.4 method_list_t

上一步找到method_list_t,查看源码,继承entsize_list_tt,可以看到entsize_list_tt是一个容器,我们从源码中找到遍历的迭代器是get方法

p $10.get(0)**
(method_t) $11 = {}
4.2.5 method_t

得到了method_t;去查看method_t源码 image.png 在iOS中方法是imp,我们找到imp,如上图,可以用big()方法获取如下: image.png 可以得到methodShow方法;

  • name值得是方法名
  • types 方法类型
  • imp 函数实现 image.png 可以看到有7个方法,其中.cxx_destruct是在ARC的模式下释放成员变量的析构方法,当类中有成员变量的时候,就会自动生成,property⽣成的实例变量也算,且⽗类的实例变量不会导致⼦类拥有这个⽅法。
4.2.5 types 方法类型后面的参数解析
$18 = {
  name = "setAge:"
  types = 0x0000000100003f96 "v24@0:8q16"
  imp = 0x0000000100003d60 (JQObjcBuildDemo`-[LGPerson setAge:])
}

-(void)setAge:(int)age 中 "v24@0:8q16"

1 .任何方法都默认有两个参数的,id类型的self,和SEL类型的_cmd 2. v是指返回值地址下表: Xnip2022-04-23_17-54-09.jpg 举例:

-(void)setAge:(NSInteger)age 中 "v24@0:8q16"
v                   24         @ 0    :8    q16
void(返回类型)   参数总长度     id     SEL   NSInteger
//参数的总占用空间为 8 + 8 + 8 = 24
//id从0开始占8个字节
//SEL从8开始8个字节
//NSInteger从16开始占8个字节
4.2.6 大端和小端

计算机硬件有两种储存数据的方式:大端字节序(big endian)和小端字节序(little endian)。在获取方法名的时候我们调用了big()放发,但是在不同的CPU下,不能通过此方法获取到方法名,可以通过small()方法获取;

举例来说,数值0x3311使用两个字节储存:高位字节是0x33,低位字节是0x11

  • 大端字节序:高位字节在前,低位字节在后,这是人类读写数值的方法。
  • 小端字节序:低位字节在前,高位字节在后,即以0x1122形式储存。 0x1234567的大端字节序和小端字节序的写法如下图: Xnip2022-04-23_18-26-51.jpg

4.3 属性的存储位置

找到 class_rw_t 中属性列表获取方法 properties()如图: image.png 获取property_list_t如下: image.png property_list_tt继承于property_list_t其中

struct property_t {
    const char *name;
    const char *attributes;
};

image.png

5.总结

  1. 类对象有且只有一个,类对象的本质是objc_class结构体,类对象中存储了类的父类、属性、实例方法、协议、成员变量、类方法、方法缓存等;
  2. isa的指向关系:
  • 实例对象的isa->类对象
  • 类对象的isa->元类
  • 元类的isa->根元类
  • 根元类的isa->根元类自己 image.png
  1. 元类的继承关系:
  • 父类的元类就是元类的父类

  • 根元类的父类就是NSObject

  • NSObject没有父类

  • NSObject是万类之祖 image.png