上篇内容iOS底层原理04-类,元类,bits(上)我们遗留了3个问题,
1.类方法存储到哪了?
2.ro,rw,rwe是什么?
3.苹果设计元类的目的是什么?
这篇我们来继续探索一下
类方法
上篇中得知,实例方法存放在类对象的class_rw_t中,那类方法会不会存放在元类的class_rw_t中呢?我们来实际验证一下。
我们发现。确实验证了我们的猜想,类方法是存在元类里面的,位置和类对象的静态方法位置一样。
接下来我们去看一下类方法的源码实现
双重验证了我们的猜想,源码通俗易懂,去类的元类里找静态方法列表,也就是methods,从而可以得知,类方法没什么特殊的,它也是一个静态方法,只不过是元类的。
下面会引申出来一个特别有意思的问题,苹果设计元类的目的是什么?
苹果设计元类的目的
主要的⽬的是为了复⽤消息机制。在OC中调⽤⽅法,其实是在给某个对象发送某条消息。消息的发送在编译的时候编译器就会把⽅法转换为objc_msgSend这个函数。
id objc_msgSend(id self, SEL op, ...) 这个函数有俩个隐式的参数:消息的接收者,消息的⽅法名。通过这俩个参数就能去找到对应⽅法的实现。
objc_msgSend函数就会通过第⼀个参数消息的接收者的isa指针,找到对应的类,如果我们是通过实例对象调⽤⽅法,那么这个isa指针就会找到实例对象的类对象,如果是类对象,就会找到类对象的元类对象,然后再通过SEL⽅法名找到对应的imp,然后就能找到⽅法对应的实现。
那如果没有元类的话,那这个objc_msgSend⽅法还得多加俩个参数,⼀个参数⽤来判断这个⽅法到底是类⽅法还是实例⽅法。⼀个参数⽤来判断消息的接受者到底是类对象还是实例对象。消息的发送,越快越好。
那如果没有元类,在objc_msgSend内部就会有有很多的判断,就会影响消息的发送效率。所以元类的出现就解决了这个问题,让各类各司其职,实例对象就⼲存储属性值的事,类对象存储实例⽅法列表,元类对象存储类⽅法列表,符合设计原则中的单⼀职责,⽽且忽略了对对象类型的判断和⽅法类型的判断可以⼤⼤的提升消息发送的效率,并且在不同种类的⽅法⾛的都是同⼀套流程,在之后的维护上也⼤⼤节约了成本。
所以这个元类的出现,最⼤的好处就是能够复⽤消息传递这套机制。不管你是什么类型的⽅法,都是同⼀套流程。
在objc底层没有类⽅法和实例⽅法的区别,都是函数。
写个伪代码可以直观的对比一下有无元类的复杂程度
ro,rw,rwe是什么
我们在探索的过程中发现,有些东西在ro中发现,有的在rw中发现,另外还发现了又rwe,那这些东西又是什么关系呢?
ro顾名思义就是readonly,它在内存中属于clean memory,在编译的时候产生。而rw就是readwrite,它在内存中的dirty memory,在运行的时候生成的。
根据WWDC20的介绍视频,通过分离出那些永远不会被改变的数据可以把大部分的类数据存储为clean memory。当一个类首次被使用,runtime会为它分配额外的存储容量,并将class_ro的内存存放到这个存储容量中,这个存储容量就是class_rw_t。在class_rw_t中,还存储了只有在运行时才会生成的新信息。
生成的信息包括有:通过使用First Subclass和Next sibling Class将类链接成一个树状结构、当category被加载时,它可以向类中添加新方法、可以动态添加属性和协议等。此时,class_rw_t中间即含有不可变的内容,也包含了动态添加的内容。
接下来Apple继续将class_rw_t进行分割,将可变的部分与不可变的部分分开来,这可以大大减小class_rw_t所占的内存空间,对于那些确实需要额外信息的类,将额外的信息放到class_rw_ext_t中。class_rw_ext_t生成的条件有两个,一是用过runtime的Api进行动态修改的时候;二是有分类的时候,且分类和本类都为非懒加载类的时候。实现了+load方法即为非懒加载类。
最终的类的结构就如下所示。类中包含一份完整的class_ro_t,当内存空间不足时,可以将这部分的内容从内存中移除,有需要时再从磁盘中加载。class_rw_t会将class_ro_t的内容放到自己里面,然后再将当前类的category里的方法、属性、协议也放到自己中。最后,再将那些会可变的部分与不可变的部分分开来。
class_ro_t是在编译的时候⽣成的。当类在编译的时候,类的属性,实例⽅法,协议这些内容就存在class_ro_t这个结构体⾥⾯了,这是⼀块纯净的内存空间,不允许被修改。class_rw_t是在运⾏的时候⽣成的,类⼀经使⽤就会变成class_rw_t,它会先将class_ro_t的内容"拿"过去,然后再将当前类的分类的这些属性、⽅法等拷⻉到class_rw_t⾥⾯。它是可读写的。
class_rw_ext_t可以减少内存的消耗。苹果在wwdc2020⾥⾯说过,只有⼤约10%左右的类需要动态修改。所以只有10%左右的类⾥⾯需要⽣成class_rw_ext_t这个结构体。这样的话,可以节约很⼤⼀部分内存。