前面我们学习了分类已经分类的加载流程,这一篇我们更细致的研究类和分类之间加载过程的联系,更加细致的思考一些上一篇值得深入的问题:
1、rw、ro、rwe的区别和联系,为什么要这么设计?
2、类和分类搭配加载过程。
rw、ro、rwe的区别和联系
根据WWDC2020了解到rw以及rwe的作用,下面我们分别来看一下ro、rw、rwe。要了解他们,我们先要了解下iOS的两种内存clean memory和dirty memory。
clean memory
:是指加载后不会发生改变的内存。class_ro_t 就属于clean memory, 因为它是只读的(ro 代表 readonly)。
dirty memory
:是指在进程运行时会发生更改的内存,class_rw_t 是 dirty memory(rw 代表 read write)。
而dirty memory
要比clean memory
更加昂贵,前者一经使用就必须存在,可以通过runtime往类里写入新的数据,所以dirty memory是动态的,而clean memory是经过编译就不可以再写入的,大小是固定的,是可移除的,可以节省更多的内存空间,并且iOS是不会使用swap进行转换,所以dirty memory在iOS的开销更大一些,也就是所谓的更加"昂贵"。
也是因为这两种内存的存在,所以类的数据就被分成了相应的这两个部分:
- 一经编译,就不可修改的数据。(ro)
- 运行过程中可修改的数据。(rw)
ro
:数据是只读的,它属于clean Memory
。它是从沙盒读取,ro的数据在编译的时候就已经确定了。
rw
:数据是可读可写的,它属于dirty Memory
。rw的数据存放的是运行时动态修改的数据。
rwe
:是苹果为rw做的优化,从rw拆出来那些平时不常用的部分,以减少rw的开销,大约 90% 的类从来不需要这些扩展数据,这在系统范围内可节省大约14MB 的内存。
所以综上,rw是针对于脏内存进行数据存储,ro是clean内存,而rwe是rw的优化,可以理解为他的扩展,之所以这么设计,是为了保证clean的数据越多越好,尽可能的不被污染,那么效率也就更高,如果动态添加的数据和一开始编译时期就被加载的数据都混合在一起,那么每次去查询都是去查询一个动态的空间,空间的size等都是变量,肯定就没有一块固定的不变的内存查询效率高。他这样做尽可能的保证有一块内存空间是固定的干净的,就优化了效率节省了一部分的查询时间。(一部分是个人理解)
类和分类搭配的加载过程
1.非懒加载类+非懒加载的分类
- 非懒加载类流程:map_images->map_images_nolock->_read_images->realizeClassWithoutSwift->methodizeClass->attachToClass->load_images->loadAllCategories->load_categories_nolock->attachCategories->attachLists
调试流程,分别在attachToClass和attachCategories为类和分类做好相应的断点,筛选下为想要的类的时候,进行进一步的断点调试。
2.懒加载类+非懒加载的分类
map_images->map_images_nolock->_read_images->realizeClassWithoutSwift->methodizeClass->attachToClass
这个时候通过调试发现,并没有走attachCategories
的逻辑。
3.非懒加载类+懒加载的分类
调试结果竟然和情况2是一样的!
4.懒加载类+懒加载的分类
_objc_msgSend_uncached->lookUpImpOrForward->realizeAndInitializeIfNeeded_locked->initializeAndLeaveLocked->initializeAndMaybeRelock->realizeClassMaybeSwiftAndUnlock->realizeClassMaybeSwiftMaybeRelock->realizeClassWithoutSwift->methodizeClass->attachToClass
以上只讨论了单个类的情况,那么如果有多个分类都实现了load呢?接着调试看下。
懒加载类+多个非懒加载分类
一共三个分类,一个实现了+load一个不实现,此时调试走的逻辑是:
map_images->map_images_nolock->_read_images->realizeClassWithoutSwift->methodizeClass->attachToClass。
没有调用attachCategories
的逻辑!一共三个分类,两个实现了+load一个不实现,此时调试走的逻辑是:
load_images->prepare_load_methods->realizeClassWithoutSwift->methodizeClass->attachToClass->attachCategories
这个走了attachCategories
逻辑!-
一共四个分类,分类B和分类C都实现了-(void)BCBC方法,B的实现打印的是"BCBC",C的实现打印的是"CBCB",分类A和分类B实现+load,分类C、D不实现:load_images->prepare_load_methods->realizeClassWithoutSwift->methodizeClass->attachToClass->attachCategories,走了
attachCategories
逻辑!。并且分类的打印情况如下:
并且换一下分类B和分类C的编译顺序,则最后会打印后边编译的分类同名方法,即使其中一个不用声明,光有实现,也会打印后编译的分类中的实现内容。(这个过程后面有时间具体探索和分析下,这个部分先略过)
所以经过上面的测试,发现类不实现+load,分类至少两个实现了+load以上就会走prepare_load_methods->realizeClassWithoutSwift->methodizeClass->attachToClass->attachCategories
流程。
非懒加载类+多个非懒加载分类
- 一共四个分类A、B、C、D都不实现+load,主类实现了+load,不会走
attachCategories
逻辑。 - 一共四个分类只有A实现了+load,主类实现了+load,这时候会走
attachCategories
逻辑,并且通过loadAllCategories
走到attachCategories。 - 一共四个分类A、B实现了+load,主类实现了+load,这时候会走
attachCategories
逻辑,并且也是通过loadAllCategories
走到attachCategories。
所以非懒加载类,只要有一个分类实现了+load,会通过loadAllCategories来到attachCategories
emmm为什么会这样啊?分类的数量还有实现+load会影响分类的加载流程,会走不同的方法进入到attachCategories,或者有的即使实现了分类和+load也不会来到attachCategories
!!!!😳😳😳😳😳😳😳😳
我们通过查看这几种情况下的ro、rw或者rwe中method_list情况来观察下。
-
第一种情况,分类有+load而主类没有,是没有走
attachCategories
的逻辑情况,此时我们通过调试ro,发现在_read_images
方法中,尽管主类没有实现+load,但是还是走了非懒加载的方法。
并且通过打印调试ro中的baseMethod的内容发现,此时分类的方法已经全都被加到了baseMethod中。
所以,这种情况,是在dyld的时候就直接把分类的方法合并到了ro的baseMethod里。 而主类有+load,分类没有的情况通过调试和上面👆🏻这个过程一样,也是ro中的baseMethodList已经有了分类中的方法,不需要再attachCategories都。
然后分类和主类都没实现+load的情况,上面的结论是也不再走
attachCategories
,来到realizeClassWithoutSwift打印ro的数据一样也是分类的方法已经添加完了,只是发起的流程由lookupImpOrForward发起的了。-
最后,多个分类不全有+load的情况,主类实现了+load,通过调试,发现分类的方法并没有全部加入到ro的baseMethod中,此时只有6个。
然后我打印下这六个方法分别是:
都是主类LGPerson的方法,此时分类的方法并没有加进来!
然后我们接着调试,来到方法methodizeClass
,此时rwe打印还是nil,而方法methodizeClass基本都是从rwe取数据,所以这个时候分类的信息还没有被加载进来,然后继续调试就来到了load_images
->loadAllCategories
->load_categories_nolock
,然后通过attachCategories初始化了rwe!
然后就接着去走prepareMethodLists和attachList流程。
所以这个流程相对来说比较繁琐,会多走一些方法来把分类的方法插入到methodList,并且要创建rwe,系统开销比前几种会大一些。所以分类中的+load要慎用,会影响启动速度,增加不必要的开销!
学习本身就是逆人性的过程,看了两三天这个地方,头大头大,还好有收获,加油加油!