在上一篇文章中,我们分析了类的加载,并且已经摸到了分类的信息,那么是怎么加到我们类里面去的呢,还有在什么时候加的呢,这是本篇文章将进行探讨的
分类怎么加载到类里面的
我们接着上一篇文章的结尾讲, 在attachCategories
里面拿到分类信息进行排序完了之后会就来到
if (mcount > 0) {
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle);
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
if (flags & ATTACH_EXISTING) flushCaches(cls);
}
我们先来看 attachLists
,点到源码里面去
在这个里面分为三种情况:
首先第一种情况: 0 lists -> 1 list
,很简单,就是从0添加到1的一个过程。第一种情况用于添加本类
再来看到第二种情况: newCount = oldCount(1) + addedCount
代表原来的容量加上添加的容量之和,拿到新的容量大小之后开辟了一个newCount
大小的数组,然后把oldList
放在addedLists
的后面。第二种情况用于添加第一个分类
第三种情况: 多个lists
,前面的步骤跟第二种情况是一样的,区别就在于先添加老的数组
。 老数组添加的位置是以新数组数量为开始位置,新数组的开始位置就是起始位置, 相当于旧的数组放在新数组的后面
,越晚加进来的,越在前面。第三种情况用于添加多个分类
为什么把分类数组放在前面,还有就是越晚加进来的越放在前面,因为我们分类里面的方法是优先调用的,所以得加在前面,而且越晚加进来的分类越先调用。
说了这么多,我们通过断点调试验证一下这三种情况,首先来到attachCategories
里面的rwe
创建
来到rwe
创建的地方,初始化后我们打印出rwe
为空,然后拿到本类的methodList
进行rwe->methods.attachLists(&list, 1);
, 我们点到attachLists
里面打个断点看会不会来到第一种情况
本类LGPerson
确实来到了第一种情况,进来将LGPerson
里面的baseMethods
赋值到了rwe
里面的methods
,此时rwe
就有值了
同样的,再来验证下第二种情况和第三种情况,前面说过的第二种情况是添加第一个分类,第三种情况是添加多个分类
在添加分类的时候,首先通过指针地址的平移拿到要添加的分类
if (mcount > 0) {
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle);
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
if (flags & ATTACH_EXISTING) flushCaches(cls);
}
平移到要添加的分类之后传给attachLists
里面的addedLists
进行添加
第二种情况也得以验证, 接下来再来看看LGB会不会来到第三种情况
最后我们来调用一下kc_instanceMethod1
,看看是不是调用LGB
分类里的方法,因为我们前面说过越晚加进来的,越在前面,越先调用
完美,到目前为止我们已经验证了分类是如何加载到类的,那么接下来再来看在什么时候加进去的
分类在什么时机加载到类里面的
我们先来看下整个加载的流程,分类其实就是在load_categories_nolock
里面进行加载的,那么我是怎么知道呢,很简单,全局搜索一下attachCategories
看看在哪些地方进行调用了,或者在attachCategories
打印下bt
通过堆栈信息我们推断出加载时机,就如我下面这幅流程图:
这种时机的前提是主类和分类都实现了load
方法,那么我们假设一下,假设LGB
分类不实现lod
方法会是怎么样的,我们运行打印一下
结果还是走了LGB
这个分类。 这就意味着,主类和其中一个分类实现了load
方法,其它分类实不实现load
方法都是一样的效果。也就是说只要有一个分类实现了load
方法,其它分类都会被标记成非懒加载分类。
苹果这么做就是一次性把所有分类全部处理了,免得每一次还得重新开辟计算,做一系列操作。
我们再来看看把分类的load
全部去掉,就留主类的load
方法,然后运行
发现打印的流程少了很多,但还是打印了LGB
里面的kc_instanceMethod1
,这是啥情况,我们读一下MachO
,看一下类里面加载了哪些方法
可以看出只要主类有实现了load
方法,那么在编译的时候分类的方法也都会加到类里面去
接下来看一下第三种,主类没有实现load
方法,分类也没有实现load
方法
通过这个打印的我们发现realizeClassMaybeSwiftMaybeRelock
只有在第一次消息的时候才会来到,说明都没有实现load
方法的时候推迟到了第一次消息的时候,我们在readClass
里面看一下data
有没有16个方法,如果有说明就不需要load_images
通过缓慢的加载进来
再来看最后一种,主类没有实现load
方法,分类实现load
方法,运行打印一下
打印完了之后,同样我们看一下ro
里面的方法
发现只有8个了,通过打印可以看出只有本类里面的8个方法了,那么分类的去哪了,其实在刚开始的时候已经说过了,在load_categories_nolock
里面
从而验证了这里的流程跟我前面发的那幅流程图一摸一样。
但是由于本类没有实现load
方法,按理说应该是在第一次消息的时候实现去实现这个类,但是我们在readClass
的时候已经读到这个类了,说明有了分类就提前加载了。从而得出一个结论:
只要分类实现了load方法,就要迫使主类提前加载,如果分类和主类都没实现load方法,就会在第一次消息的时候加载`
所以能不用load
方法就不用load
方法,不要在应用程序启动前加载很多东西。
最后,把整个类和分类搭配加载的流程用一张PPT来结束
iOS 底层原理 文章汇总