类的底层原理(上)

207 阅读5分钟

一、类对象的isa指针的指向

1.此处证明类对象有且只有一个,因为内存地址都是一样的 WechatIMG267.png 2.我们从源码中找到Class,发现类对象就是一个objc_class结构体 WechatIMG268.png 3.实例对象的isa指针指向类对象, WechatIMG269.png 类对象的isa指针是指向哪里呢?我们来证明一下 通过x/4gx打印一下p对象四个8字节的内存地址,得到p对象的isa, WechatIMG270.png 我们怎么通过isa指针来获取LLPerson类对象呢?(可位移或用isa&ISA_MASK) 我们使用isa&ISA_MASK方式 WechatIMG271.png 以下是得出了LLPerson类对象 WechatIMG272.png 接下来我们在打印类对象的内存地址,得出了的还是LLPerson WechatIMG273.png 现在看得出的两个都是LLPerson,但是他们的内存地址为什么不一样呢?

我们看testClassNum()函数里面打印出来的LLPerson内存地址与第一个LLPerson内存地址一样,那么第二个LLPerson就是他的元类(LLPerson的元类) WechatIMG124.png

元类的名字和类对象的名字是一模一样的

接下来我们继续打印元类对象的内存地址,得到了NSObject WechatIMG125.png

那么这个NSObject和系统的NSObject是同一个么? 我们验证一下,打印出来的内存地址并不一样,所以不是同一个 WechatIMG127.png 我们继续打印第一个NSObject的内存地址,得出的内存地址还是他自己 WechatIMG128.png

我们打印第二的内存地址,我们发现得到的地址与第一个NSObject的内存地址一样

我们叫第一个NSObject根元类,根元类ISA指针指向自己的 WechatIMG129.png

实例对象ISA --> 类对象ISA --> 元类ISA --> 根元类 --> 根元类自己

根类NSObject ISA --> 根元类

二、类与元类的关系

有以下代码我们得出元类的父类就是父类的元类

LLTeacher继承了LLPerson,LLTeacher元类的父类就是LLPerson(LLPerson的元类) WechatIMG131.png 我们看NSObject的父类是什么,打印出来的是null WechatIMG132.png 那么根元类的父类呢,我们看打印出来的就是NSObject WechatIMG133.png 以下图片得出类之前的关系: 子类的superClass就是父类,父类的superClass就是根类,根类的superClass是nil, 子类的元类的父类就是父类的元类,父类的元类的父类就是根元类,根元类的父类就是根类 NSObject万类之祖 WechatIMG134.png 以下图片得出ISA之指针之前的关系: 子类的实例对象的ISA指向子类的ISA,子类的ISA指向子类的元类的ISA,子类的元类的ISA指向根元类的ISA WechatIMG135.png

三、类对象结构

1.类对象就是一个objc_class结构体,里面存储着ISA(8字节),superClass(8字节),cache(16字节),bits,今天我们先来研究一下bits WechatIMG136.png 2.内存平移

我们先看这是定义了两个整型a、b,打印出的内存地址 WechatIMG290.png 再看定义了两个类对象,打印出的内容,这是两个对象,所以开辟出来的内存地址不同 WechatIMG291.png 再看定义了整型的c数组,我们直接打印c的内存地址就是c数组里的第一个元素的内存地址,当我们把c赋值给d指针的时候,d的内存地址就与c的内存地址一样的,当我们打印d+1的时候,他的内存地址就跟c的第二个元素的内存地址相同,这就是内存平移,这里平移的大小就是4,因为int是4字节 WechatIMG292.png 那看这个for循环,通过 * 来取这个 d+i 地址里面的值( * 就是取地址里面的值) WechatIMG293.png

3.通过内存平移获取objc_class中的bits里面的数据 WechatIMG294.png 以LLPerson为例,来获取LLPerson的bits, WechatIMG297.png : 前面的内容LLPerson类对象的首地址,获取bits是在首地址的基础上平移32个字节(isa的8字节+superclass的8字节+cache的16字节),就是0x1000080e0 --> 0x100008100,那我们来打印一下,使用class_data_bits_t强转一下(由于编写运行代码的时间不同,首地址在后面发生了变化) WechatIMG298.png 我们看class_data_bits_t 给我们提供了class_rw_t类型的data方法 WechatIMG302.png data()中存储了--方法、属性、协议 WechatIMG311.png 看打印结果,method_list_t中可以看出有7个方法 WechatIMG329.jpeg 如何继续获取呢,我们看$6是method_list_t,我看找到method_list_t,它是继承entsize_list_tt WechatIMG180.png

entsize_list_tt可以理解成一个容器,容器中必定会有迭代器,迭代器就是可以通过某种方法遍历的,使用.get(0)获取第一个方法 WechatIMG307.jpeg 打印出的这7个方法,都是LLPerson中的,这里看出没有类方法,因为类方法存储在元类里面 .cxx:.在arc模式下,用来释放成员变量的(包括通过属性生成的成员变量)

type:描述参数 WechatIMG330.jpeg 上面我们看到了获取具体方法的时候使用到了.big(),这里涉及了大端小端 大端:高位字节存放内存的低地址段,低位字节存放内存的高地址段(用于Intel)

小端:高位字节存放内存的高地址段,低位字节存放内存的低地址段(用于M1) 例: 0x12345678 0x10001 0x10002 0x10003 0x10004 大端:0x12 0x34 0x56 0x78 小端:0x78 0x56 0x34 0x12

WechatIMG331.png 当我们的设备是M1芯片的时候,他就是小端,小端的打印方式是这样的,使用了getDescription() 方法打印,能够获取到name和types WechatIMG184.png WechatIMG185.png WechatIMG182.png WechatIMG183.png

4.我们按照获取方法的方式,再来获取一下属性,基本流程同methods() WechatIMG172.jpeg