iOS 类的加载(下)
在上一篇文章 类的加载(上) 中,理解了 类是如何从Mach-O加载到内存 中,这次我们来解释下 分类 是如何 加载 到 类 中的,以及 分类和类搭配使用 的情况
前提 :在main中定义LGperson的分类LG
方式一:通过clang
同时,我们发现了一个 问题 :查看看 _prop_list_t ,明明 分类 中定义了 属性 ,但是在底层编译中并没有看到属性,如下图所示,这是因为 分类中定义的属性没有相应的set、get方法 ,我们可以通过 关联对象 来设置(关于如何设置 关联对象 ,我们将在后续的扩展中进行说明)
方式三:通过objc源码搜索 category_t
综上所述,分类的 本质 是一个 _category_t 类型
前提:创建LGPerson的两个分类:LGA、LGB
在上一篇 类的加载(上) 文章中的 realizeClassWithoutSwift -> methodizeClass -> attachToClass -> load_categories_nolock -> extAlloc ->attachCategories 中提及了rwe的加载,其中分析了分类的 data 数据 时如何 加载到 类 中的,且分类的加载顺序是: LGA -> LGB 的顺序加载到类中,即 越晚加进来,越在前面
其中查看 methodizeClass 的源码实现,可以发现 类的数据 和 分类的数据 是分开处理的,主要是因为在 编译阶段 ,就已经确定好了 方法的归属位置 (即 实例方法 存储在 类 中, 类方法 存储在 元类 中),而 分类 是后面才加进来的
其中分类需要通过 attatchToClass 添加到类,然后才能在外界进行使用,在此过程,我们已经知道了分类加载三步骤的后面两个步骤,分类的加载主要分为3步:
下面我们来探索 分类数据的加载时机 ,以 主类 LGPerson + 分类 LGA、LGB 均实现+load 方法为例
通过第二步数据准备反推第一步的加载时机
addToClass 方法中,这里经过调试发现,从来不会进到if流程中,除非加载两次,一般的类一般只会加载一次
全局搜索 load_categories_nolock 的调用,有两次调用
通过堆栈信息分析
所以综上所述,该情况下的 分类的数据加载时机 的反推 路径 为: attachCategories -> load_categories_nolock -> loadAllCategories -> load_images
而我们的分类加载正常的流程的路径为: realizeClassWithoutSwift -> methodizeClass -> attachToClass ->attachCategories
其中正向和反向的流程如下图所示:
我们再来看一种情况:主类+分类LGA实现+load,分类LGB不实现+load方法
总结 : 只要有一个分类是非懒加载分类,那么所有的分类都会被标记位非懒加载分类 ,意思就是 加载一次 已经开辟了rwe ,就不会再次懒加载,重新去处理 LGPerson
通过上面的两个例子,我们可以大致将类 和 分类 是否实现+load的情况分为4种
即 主类实现了+load方法,分类同样实现了+load方法 ,在前文分类的加载时机时,我们已经分析过这种情况,所以可以直接得出结论,这种情况下
其调用路径为:
即 主类实现了+load方法,分类未实现+load方法
从上面的打印输出可以看出,方法的顺序是 LGB—LGA-LGPerson类 ,此时分类已经 加载进来了,但是还没有排序,说明在没有进行非懒加载时,通过 cls->data 读取 Mach-O 数据时,数据就已经编译进来了,不需要运行时添加进去* 来到 methodizeClass 方法中断点部分
来到 prepareMethodLists 的for循环部分
来到 fixupMethodList 方法中的 if (sort) { 部分
走到 mlist->setFixedUp(); ,在读取list
通过打印发现,仅 对同名方法进行了排序 ,而分类中的其他方法是不需要排序的,其你imp地址是有序的(从小到大) -- fixupMethodList 中的排序 只针对 name 地址进行排序
总结 : 非懒加载类 与 懒加载分类 的数据加载,有如下结论:
即 主类和分类均未实现+load方法
总结 : 懒加载类 与 懒加载分类 的 数据加载 是在 消息第一次调用 时记载
即 主类未实现+load方法, 分类实现了+load方法
总结 : 懒加载类 + 非懒加载分类 的数据加载, 只要分类实现了load,会迫使主类提前加载 ,即 主类 强行转换为 非懒加载类样式
类和分类 搭配使用,其数据的 加载时机 总结如下:
如下图所示
load_images 方法的主要作用是加载镜像文件,其中最重要的有两个方法: prepare_load_methods (加载) 和 call_load_methods (调用)
进入 prepare_load_methods 源码
进入 call_load_methods 源码,主要有3部分操作
综上所述, load_images 方法整体 调用过程 及 原理图示 如下
主要分为两步