013:分类的加载:realizeClassWithoutSwift -> methodizeClass -> attachToClass ->load_categories_nolock->attachCategories, loadImage的加载
问题
懒加载分类会在编译器把数据加载
只要有非懒加载分类会走attachCategories来加载分类数据
1:methodizeClass内部有对分类的处理
2:attachToClass
3:attachCategories函数:绑定分类。
4:extAllocIfNeeded:-> extAllocIfNeeded:
5: extAlloc-->>初始化rwe
6:prepareMethodLists函数排序
7:fixupMethodList: 修复函数
8:attachLists:完成数据的绑定
分类的加载,总得来说有2个大的调用路径:
1:map_images-> map_images_nolock-> _read_images有2个可能路径:
路径一: 第8步 分类的处理-> load_categories_nolock-> attachCategories
路径二: 第9步 实现非懒加载类-> realizeClassWithoutSwift-> methodizeClass-> attachToClass-> attachCategories
2:load_images-> loadAllCategories-> load_categories_nolock-> attachCategories, 非懒加载类和非懒加载分类
目录
预备
正文
1:分类的本质
1:通过 objc 源码搜索 category_t

2:分类中定义的方法:methodlist

其中有一个方法,格式为:sel+签名+函数地址。这个就是我们前面分析过的 method_t的结构,我们在 objc 源码里面搜索 method_t。
搜索method_t,其中对应关系如下
name 对应 sel
type 对应 方法签名
imp 对应 函数地址

3:分类中定义的属性

同时,我们也注意到一个问题,在分类中,我们定义了两个属性,但是编译成c++之后,并没有看到相关属性,这是因为 分类中定义的属性没有相应的set和get方法的实现,只有方法的声明。
4:总结
分类的本质就是一个 category_t的结构体类型

2、分类的加载
我们先给 LGPerson 创建两个分类 LGA 和 LGB
1:realizeClassWithoutSwift -> methodizeClass -> attachToClass -> load_categories_nolock ->extAllocIfNeeded->attachCategories->attachLists(将方法加载到class())中提及了rwe的加载,
2:其中分析了分类的data数据 时如何 加载到类中的,且分类的加载顺序是:LGA -> LGB的顺序加载到类中,即越晚加进来,越在前面
其中查看methodizeClass的源码实现,可以发现类的数据和 分类的数据是分开处理的,主要是因为在编译阶段,就已经确定好了方法的归属位置(即实例方法存储在类中,类方法存储在元类中),而分类是后面才加进来的
其中分类需要通过 attachLists方法添加到类之后,外界才能进行调用,在此过程,我们已经知道了分类加载三个步骤中的后面两个步骤
- 分类数据
加载时机: 根据 类和分类是否实现 load 方法来区分不同的时机。 - 在
attachCategories方法中准备分类数据 - 在
attachLists中将分类数据添加到主类
分类的数据加载时机的反推路径为:attachCategories -> load_categories_nolock -> loadAllCategories -> load_images
而我们的分类加载正常的流程的路径为:realizeClassWithoutSwift -> methodizeClass -> attachToClass ->attachCategories
3:总结:只要有一个分类是非懒加载分类,那么所有的分类都会被标记位非懒加载分类,意思就是加载一次 已经开辟了rwe,就不会再次懒加载.
4:extAllocIfNeeded初始化rwe。1.分类 2.addMethod 3.class_addProtocol 4._class_addProperty

3、类和分类的搭配使用
5.1 非懒加载类 + 非懒加载分类
- 类的数据加载是通过
_getObjc2NonlazyClassList加载,即对ro、rw的操作和对rwe赋值初始化,在extAlloc方法中。 - 分类的数据加载是通过
load_images加载到类中的。
调用路径为
map_images -> map_images_nolock -> _read_images -> readClass -> _getObjc2NonlazyClassList -> realizeClassWithoutSwift -> methodizeClass -> attachToClass,此时的mlists是一维数组,然后走到load_images部分。load_images -> loadAllCategories -> load_categories_nolock -> load_categories_nolock -> attachCategories -> attachLists,此时的mlists是二维数组。
总结
1. 非懒加载类 + 非懒加载分类,其数据的加载在 load_images方法中,首先对类进行加载,然后把分类的信息贴到类中。
4. 懒加载类 + 非懒加载分类,只要分类实现了load,会迫使主类提前加载,即在 _read_images中不会对类做实现操作,需要在 load_images方法中触发类的数据加载,即rwe初始化,同时加载分类数据。
3 非懒加载类 + 懒加载分类,其数据加载在 read_image就加载数据,数据来自data,data在编译时期就已经完成,即data中除了类的数据,还有分类的数据,与类绑定在一起。
4 懒加载类 + 懒加载分类,其数据加载推迟到 第一次消息时,数据同样来自data,data在编译时期就已经完成。
4:attachClass

memcpy(开始位置,放置内容,占用大小) : 内存拷贝
memmove(开始位置,移动内容,占用大小): 内存平移
LRU算法:
-
Least Recently Used的缩写,最近最少使用算法,越容易被调用(访问)的放前面。 -
回想一下,不管我们是
动态插入函数,还是添加分类,一定是有需求时才这么操作。而新加入的数据,明显访问频率会高于默认模板内容。所以我们addedLists使用LRU算法,将旧数据放在最后面,新数据永远插入最前面。 这样可以提高查询效率,减少运行时资源的占用。
addedLists[0]赋值给list,是一维数组。(首次加载是
本类数据在extAllocIfNeeded时,从macho中读取ro中的对应数据加入
二维数组,旧数据插入后面,新数据插入前面:将数组
扩容到newCount大小->
array()的count记录个数-> 如果有
旧数据,插入到lists容器尾部-> 调用
memcpy内存拷贝,从array()首地址开始,将addedLists插入,占用addedCount个元素大小。
1->多的操作,也是旧数据移到后面,新数据插入前面将数组
扩容到newCount大小->
array()的count记录个数-> 调用
memmove内存平移,从array()首地址偏移addedCount个元素位置开始,移动array()旧数据,占用oldCount个元素大小-> 调用
memcpy内存拷贝,从array()首地址开始,将新数据addedLists插入,占用addedCount个元素大小。
rwe的函数、属性、协议都是attachLists进行处理后完成的赋值。5:load_images原理分析
load_images方法的主要作用是加载镜像文件,其中最重要的有两个方法:prepare_load_methods(加载) 和 call_load_methods(调用)
1:load_images源码实现

2: 进入prepare_load_methods源码

3:add_class_to_loadable_list,主要是将load方法和cls类名一起加到loadable_classes表中

4:进入getLoadMethod,主要是获取方法的sel为load的方法
5:_getObjc2NonlazyCategoryList -> realizeClassWithoutSwift -> add_category_to_loadable_list,主要是将非懒加载分类的load方法加入表中
6:进入add_category_to_loadable_list实现,获取所有的非懒加载分类中的load方法,将分类名+load加入表loadable_categories

call_load_methods源码,主要有3部分操作-
反复调用
类的+load,直到不再有 -
调用一次分类的+load -
如果有类或更多未尝试的分类,则运行更多的+load、

call_class_loads,主要是加载类的load方法
3:call_category_loads,主要是加载一次分类的load方法
load_images方法整体调用过程及原理图示如下
主要分为两步
- 从所有的
非懒加载类和分类中的+load分别添加到表中 - 调用
类和分类的+load方法
6:rwe的加载,是执行了extAlloc方法,所以我们反向搜索,查看谁调用了extAlloc方法:只有extAllocIfNeeded和deepCopy调用了。
-
deepCopy深拷贝: 搜索deepCopy(,发现只被objc_duplicateClass调用,而是objc_duplicateClass开放使用的API接口,并没自动调用的地方。 所以此处不做考虑。 -
extAllocIfNeeded: 搜索extAllocIfNeeded(,发现有以下7处调用了它:

1.1 本类+load,分类无
提取信息如下:
- 路径: 是
map_images调用的- ro函数列表:此时
ro读取的是macho中的值,ro中已包含本类和所有函数信息(14个)。- 函数排序:
分类的函数不会覆盖本类的同名函数,而是后加载的分类函数排序在先加载的分类和本类前面。
放开断点,继续运行,发现没有进入attachCategories内部。
结论:【本类+load,分类无】的情况:数据在编译层就已经加入到data中。
1.2. 本类+load,分类+load
提取信息如下:
- 路径: 是
map_images调用的 - ro函数列表:此时
ro读取的是macho中的值,ro中仅有HTPerosn本类函数信息(8个)。
attachCategories->attachLists
1.3: 本类无,分类无
继续运行代码,没有进入attachCategories中。
1.4. 本类无,分类+load
attachCategories->attachLists,提前加载数据
1.5:本类无,分类A无 ,分类B+load
发现ro中加载好了本类和2个分类的所有数据(14个函数),没有再进入attachCategories了。
浙公网安备 33010602011771号