本篇主要研究分类
是如何加载
到类
中的,以及分类和类搭配使用
的情况
分类的本质
前提:在main中定义LGperson的分类LG
我们通过clang探索分类的本质
-
clang -rewrite-objc main.m -o main.cpp
查看底层编译,即 main.cpp,- 其中
分类
的 类型是_category_t
- 分类的倒数第二个0,表示的是
没有协议
,所以赋值为0
- 其中
-
搜索
struct _category_t
,如下所示- 其中有两个method_list_t,分别表示
实例方法
和类方法
- 其中有两个method_list_t,分别表示
-
搜索
_CATEGORY_INSTANCE_METHODS_LGPerson_
,找到其底层实现 - 其中有3个方法,格式为:
sel+签名+地址
,是method_t
结构体的属性即key -
搜索
method_t
,其中对应关系如下name
对应sel
type
对应方法签名
imp
对应函数地址
同时,我们发现了一个问题:查看看_prop_list_t
,明明分类
中定义了属性
,但是在底层编译中并没有看到属性,如下图所示,这是因为分类中定义的属性没有相应的set、get方法
,我们可以通过关联对象
来设置(关于如何设置关联对象
,我们将在后续的扩展中进行说明)
总结
综上所述,分类的本质
是一个_category_t
类型
-
有两个属性:
name(类的名称)
和cls(类对象)
-
有两个
method_list_t
类型的方法列表,表示分类中实现的实例方法+类方法
-
一个
protocol_list_t
类型的协议列表,表示分类中实现的协议 -
一个
prop_list_t
类型的属性列表,表示分类中定义的属性,一般在分类中添加的属性都是通过关联对象
来实现 -
需要注意的是,分类中的
属性
是没有set、get
方法
分类的加载
前提:创建LGPerson的两个分类:LGA、LGB
在上一篇文章中的realizeClassWithoutSwift -> methodizeClass -> attachToClass -> load_categories_nolock -> extAlloc ->attachCategories
中提及了rwe的加载,其中分析了分类的data
数据 时如何 加载到类
中的,且分类的加载顺序是:LGA -> LGB
的顺序加载到类中,即越晚加进来,越在前面
其中查看methodizeClass
的源码实现,可以发现类的数据
和 分类的数据
是分开处理的,主要是因为在编译阶段
,就已经确定好了方法的归属位置
(即实例方法
存储在类
中,类方法
存储在元类
中),而分类
是后面才加进来的其中分类需要通过
attatchToClass
添加到类,然后才能在外界进行使用,在此过程,我们已经知道了分类加载三步骤的后面两个步骤,分类的加载主要分为3步:
-
分类数据
加载时机
:根据类和分类是否实现load方法
来区分不同的时机 -
attachCategories
准备分类数据
-
attachLists
将分类数据添加到主类
中
分类的加载时机
下面我们来探索分类数据的加载时机
,以主类
LGPerson + 分类
LGA、LGB 均实现+load
方法为例
通过第二步数据准备反推第一步的加载时机
-
通过上一篇文章我们了解到,在走到
attachCategories
方法时,必然会有分类数据的加载
,可以通过反推法
查看 在什么时候调用attachCategories
的,通过查找,有两个方法中调用load_categories_nolock
方法中addToClass
方法中,这里经过调试发现,从来不会进到if流程中,除非加载两次,一般的类一般只会加载一次
-
不加任何断点,运行objc代码,可以得出以下打印日志,通过日志可以发现
addToClass
方法的下一步就是load_categories_nolock
方法就是加载分类数据
-
全局搜索
load_categories_nolock
的调用,有两次调用- 一次在
loadAllCategories
方法中 - 一次在
_read_images
方法中
- 一次在
-
但是经过调试发现,是不会走
_read_images
方法中的if流程的,而是走的loadAllCategories
方法中的 -
全局搜索查看
loadAllCategories
的调用,发现是在load_images
时调用的
通过堆栈信息分析
- 在
attachCategories
中加自定义逻辑的断点,bt
查看堆栈信息
所以综上所述,该情况下的分类的数据加载时机
的反推路径
为:attachCategories -> load_categories_nolock -> loadAllCategories -> load_images
而我们的分类加载正常的流程的路径为:realizeClassWithoutSwift -> methodizeClass -> attachToClass ->attachCategories
我们再来看一种情况:主类+分类LGA实现+load,分类LGB不实现+load方法
-
断点定在
attachCategories
中加自定义逻辑部分,一步步往下执行p entry.cat
p *$0
-
继续往下执行,会再次来到
attachCategories
方法中断住p entry.cat
p *$0
总结:
只要有一个分类是非懒加载分类,那么所有的分类都会被标记位非懒加载分类
,意思就是加载一次 已经开辟了rwe
,就不会再次懒加载,重新去处理LGPerson
分类和类的搭配使用
通过上面的两个例子,我们可以大致将类 和 分类 是否实现+load的情况分为4种
类+分类 | |||
---|---|---|---|
分类实现+load | 分类未实现+load | ||
类实现+load | 非懒加载类+非懒加载分类 | 非懒加载类+懒加载分类 | |
类未实现+load | 懒加载类+非懒加载分类 | 懒加载类+懒加载分类 |
-
【情况1】
非懒加载类 + 非懒加载分类
-
【情况2】
非懒加载类 + 懒加载分类
-
【情况3】
懒加载类 + 懒加载分类
-
【情况4】
懒加载类 + 非懒加载分类
总结:懒加载类 + 非懒加载分类
的数据加载,只要分类实现了load,会迫使主类提前加载
,即 主类 强行转换为 非懒加载类样式
总结
类和分类
搭配使用,其数据的加载时机
总结如下:
-
【情况1】
非懒加载类 + 非懒加载分类
,其数据的加载在load_images
方法中,首先对类进行加载,然后把分类的信息贴到类中 -
【情况2】
非懒加载类 + 懒加载分类
,其数据加载在read_image
就加载数据,数据来自data
,data在编译时期
就已经完成,即data中除了类的数据,还有分类的数据,与类绑定在一起 -
【情况3】
懒加载类 + 懒加载分类
,其数据加载推迟到 第一次消息
时,数据同样来自data
,data在编译时期
就已经完成 -
【情况4】
懒加载类 + 非懒加载分类
,只要分类实现了load,会迫使主类提前加载
,即在_read_images
中不会对类做实现操作,需要在load_images
方法中触发类的数据加载,即rwe初始化
,同时加载分类数据
如下图所示