一、类对象的isa指针的指向
1.此处证明类对象有且只有一个,因为内存地址都是一样的
2.我们从源码中找到Class,发现类对象就是一个objc_class结构体
3.实例对象的isa指针指向类对象,
类对象的isa指针是指向哪里呢?我们来证明一下
通过x/4gx打印一下p对象四个8字节的内存地址,得到p对象的isa,
我们怎么通过isa指针来获取LLPerson类对象呢?(可位移或用isa&ISA_MASK)
我们使用isa&ISA_MASK方式
以下是得出了LLPerson类对象
接下来我们在打印类对象的内存地址,得出了的还是LLPerson
现在看得出的两个都是LLPerson,但是他们的内存地址为什么不一样呢?
我们看testClassNum()函数里面打印出来的LLPerson内存地址与第一个LLPerson内存地址一样,那么第二个LLPerson就是他的元类(LLPerson的元类)
元类的名字和类对象的名字是一模一样的
接下来我们继续打印元类对象的内存地址,得到了NSObject
那么这个NSObject和系统的NSObject是同一个么?
我们验证一下,打印出来的内存地址并不一样,所以不是同一个
我们继续打印第一个NSObject的内存地址,得出的内存地址还是他自己
我们打印第二的内存地址,我们发现得到的地址与第一个NSObject的内存地址一样
我们叫第一个NSObject根元类,根元类ISA指针指向自己的
实例对象ISA --> 类对象ISA --> 元类ISA --> 根元类 --> 根元类自己
根类NSObject ISA --> 根元类
二、类与元类的关系
有以下代码我们得出元类的父类就是父类的元类
LLTeacher继承了LLPerson,LLTeacher元类的父类就是LLPerson(LLPerson的元类)
我们看NSObject的父类是什么,打印出来的是null
那么根元类的父类呢,我们看打印出来的就是NSObject
以下图片得出类之前的关系:
子类的superClass就是父类,父类的superClass就是根类,根类的superClass是nil,
子类的元类的父类就是父类的元类,父类的元类的父类就是根元类,根元类的父类就是根类
NSObject万类之祖
以下图片得出ISA之指针之前的关系:
子类的实例对象的ISA指向子类的ISA,子类的ISA指向子类的元类的ISA,子类的元类的ISA指向根元类的ISA
三、类对象结构
1.类对象就是一个objc_class结构体,里面存储着ISA(8字节),superClass(8字节),cache(16字节),bits,今天我们先来研究一下bits
2.内存平移
我们先看这是定义了两个整型a、b,打印出的内存地址
再看定义了两个类对象,打印出的内容,这是两个对象,所以开辟出来的内存地址不同
再看定义了整型的c数组,我们直接打印c的内存地址就是c数组里的第一个元素的内存地址,当我们把c赋值给d指针的时候,d的内存地址就与c的内存地址一样的,当我们打印d+1的时候,他的内存地址就跟c的第二个元素的内存地址相同,这就是内存平移,这里平移的大小就是4,因为int是4字节
那看这个for循环,通过 * 来取这个 d+i 地址里面的值( * 就是取地址里面的值)
3.通过内存平移获取objc_class中的bits里面的数据
以LLPerson为例,来获取LLPerson的bits,
: 前面的内容LLPerson类对象的首地址,获取bits是在首地址的基础上平移32个字节(isa的8字节+superclass的8字节+cache的16字节),就是0x1000080e0 --> 0x100008100,那我们来打印一下,使用class_data_bits_t强转一下(由于编写运行代码的时间不同,首地址在后面发生了变化)
我们看class_data_bits_t 给我们提供了class_rw_t类型的data方法
data()中存储了--方法、属性、协议
看打印结果,method_list_t中可以看出有7个方法
如何继续获取呢,我们看$6是method_list_t,我看找到method_list_t,它是继承entsize_list_tt
entsize_list_tt可以理解成一个容器,容器中必定会有迭代器,迭代器就是可以通过某种方法遍历的,使用.get(0)获取第一个方法
打印出的这7个方法,都是LLPerson中的,这里看出没有类方法,因为类方法存储在元类里面 .cxx:.在arc模式下,用来释放成员变量的(包括通过属性生成的成员变量)
type:描述参数
上面我们看到了获取具体方法的时候使用到了.big(),这里涉及了大端小端 大端:高位字节存放内存的低地址段,低位字节存放内存的高地址段(用于Intel)
小端:高位字节存放内存的高地址段,低位字节存放内存的低地址段(用于M1) 例: 0x12345678 0x10001 0x10002 0x10003 0x10004 大端:0x12 0x34 0x56 0x78 小端:0x78 0x56 0x34 0x12
当我们的设备是M1芯片的时候,他就是小端,小端的打印方式是这样的,使用了getDescription()
方法打印,能够获取到name和types
4.我们按照获取方法的方式,再来获取一下属性,基本流程同methods()