一、成员变量存放在哪里
接上篇类的底层探索(上),我们知道了class_data_bits_t 给我们提供了class_rw_t类型的data方法,那么我们继续进入class_rw_t结构体中找到class_ro_t,成员变量就存放在class_ro_t这个结构体中,
1.我们来验证一下,使用x/6gx获取类对象的地址空间,获取bits的首地址
在iOS中用ivar表示成员变量
我们获取ivars后发现是这个entsize_list_tt类型,上篇我们介绍了他是个容器,我们需要使用get()来获取容器里面的元素
获取到的ivar_t中的name就是类中的成员变量
2.为什么真正的成员变量要放在类里面,成员变量的值要放到实例对象里呢?
类的本质是结构体,结构体就相当于一个容器/模板,里面存储这方法、属性、协议,对象就是根据类的模板生成的,在创建不同的对象时,他们的成员变量的值也是不同的,不一样的值存储在不一样的对象里
二、ro、rw、rwe的解析
1.ro与rw的区别
ro:只读、不允许被修改,编译的时候生成的,纯净的内存空间
rw:可读可写,运行时生成,会把ro中的数据command+x(可理解成拷贝)到rw中
2.因为ro里面的数据是可读的,不可修改,所以我们要将ro中的内容拷贝一份,对拷贝的内容进行修改,所以在类中就同时存在两份相同的数据,当内存不够的时候ro才会加载、移除
3.苹果是怎么拷贝ro的
通过class_rw_ext_t(简称rwe)结构体拷贝过来的,我们只有10%的类需要动态修改,所以只需要拷贝这10%的内容,减少了内存的消耗
4.生成class_rw_ext_t的条件
使用分类的时候,分类和本类必须是非懒加载的类
通过runtime的API进行动态修改
5.程序在编译时生成ro,类在使用的时候会生成rw,当我们需要动态的修改类中的方法和属性的时候,我们就需要将ro中的数据拷贝一份rwe(rwe中没有成员变量,因为成员变量不可以被修改,runtime的API中也没有添加成员变量的内容), 这时我们在使用这个类的时候rw中就存在了两份一模一样的ro数据,这样会增加内存的消耗,基于这一点苹果会进行区分,只将需要动态修改的10%那一部分进行拷贝到rwe中,这样就减少了内存的消耗
runtime给我们提供了api给我们的类动态的添加方法和属性
三、类方法存在哪里
1.类方法是存在元类里面的,我们来验证一下
2.元类的作用:复用消息机制,增加消息发送速度,节约维护成本
在oc中调用方法,就是在编译的时候编译成objc_msgSend(),objc_msgSend()有两个默认参数消息的接受者,消息的方法名,可通过消息的接受者或消息的方法名就能找到这个方法对应的实现,当调用方法的时候会通过objc_msgSend()第一个参数消息接受者的ISA指针找到对应的类,
如果消息接受者调用的是类对象,那么会通过ISA指针去元类找方法的实现, 如果消息接受者是实例对象,那么会通过实例对象ISA指针去类里面找方法的实现
如果没有元类的话,就不能通过ISA指针去找,就应该判断是实例对象还是类对象,还要判断是实例方法还是类方法,我们的消息的发送最重要的目的就是快,如果有太多的判断就会变得很复杂,影响消息发送效率,那么这里就体现了元类的作用,实例方法、成员变量存储在类中,类方法存储在元类中,无须判断,这就叫单一原则
四、runtime API的实现
1.获取类的成员变量
2.获取类的属性
3.获取类的实例方法
4.获取实例方法和类方法的对比,在类中并没有获取到类方法,在元类中也没有获取到实例方法
当我们进入到class_getClassMethod方法中,发现它调用的是class_getInstanceMethod方法
苹果设计元类的目的并不是存储类方法,而是复用消息机制
5.方法的实现
方法的实现是存储在bits中的method_t,IMP和SEL都是存储在method_t
我们看在类中能找不到类方法的实现,在元类中也找不到实例方法的实现的时候,会返回 _objc_msgForward进行消息转发