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
了。