iOS底层-分类的加载原理

分类的本质

在main中定义LGperson的分类LG


图1

探索分类的本质,有以下三种方式

【方式一】通过clang
【方式二】通过Xcode文档搜索Category
【方式三】通过objc源码搜索 category_t

方式一:通过clang
【方式一】clang -rewrite-objc main.m -o main.cpp 查看底层编译,即 main.cpp,

其中分类的 类型是_category_t
分类的倒数第二个0,表示的是没有协议,所以赋值为0

图2

搜索struct _category_t,如下所示

其中有两个method_list_t,分别表示实例方法 和 类方法


图3

搜索CATEGORY_INSTANCE_METHODS_LGPerson,找到其底层实现

图4

其中有3个方法,格式为:sel+签名+地址,是method_t结构体的属性即key


图5

搜索method_t,其中对应关系如下
name 对应 sel
type 对应 方法签名
imp 对应 函数地址


图6

方式二:通过Xcode文档搜索 Category

如果不会clang,可以通过Xcode文档搜索 Category


图8

方式三:通过objc源码搜索 category_t

还可以通过objc源码搜索category_t类型


图9

总结

综上所述,分类的本质 是一个_category_t类型

有两个属性:name(类的名称) 和 cls(类对象)

有两个 method_list_t类型的方法列表,表示分类中实现的实例方法+类方法

一个protocol_list_t类型的协议列表,表示分类中实现的协议

一个prop_list_t类型的属性列表,表示分类中定义的属性,一般在分类中添加的属性都是通过关联对象来实现

需要注意的是,分类中的属性是没有set、get方法

分类的加载

前提:创建LGPerson的两个分类:LGA、LGB

图10

其中查看methodizeClass的源码实现,可以发现类的数据和 分类的数据是分开处理的,主要是因为在编译阶段,就已经确定好了方法的归属位置(即实例方法存储在类中,类方法存储在元类中),而分类是后面才加进来的


图11

其中分类需要通过attatchToClass添加到类,然后才能在外界进行使用,在此过程,我们已经知道了分类加载三步骤的后面两个步骤,分类的加载主要分为3步:

分类数据加载时机:根据类和分类是否实现load方法来区分不同的时机

attachCategories准备分类数据

attachLists将分类数据添加到主类中

分类的加载时机

下面我们来探索分类数据的加载时机,以主类LGPerson + 分类LGA、LGB 均实现+load方法为例

通过第二步数据准备反推第一步的加载时机

通过上一篇文章我们了解到,在走到attachCategories方法时,必然会有分类数据的加载,可以通过反推法查看 在什么时候调用attachCategories的,通过查找,有两个方法中调用

load_categories_nolock方法中

图12

addToClass方法中,这里经过调试发现,从来不会进到if流程中,除非加载两次,一般的类一般只会加载一次


图13

不加任何断点,运行objc代码,可以得出以下打印日志,通过日志可以发现addToClass方法的下一步就是load_categories_nolock方法就是加载分类数据

图14

全局搜索load_categories_nolock的调用,有两次调用

一次在loadAllCategories方法中

图15

一次在_read_images方法中


图16

但是经过调试发现,是不会走_read_images方法中的if流程的,而是走的loadAllCategories方法中的


图17

全局搜索查看loadAllCategories的调用,发现是在load_images时调用的
图18

通过堆栈信息分析

在attachCategories中加自定义逻辑的断点,bt查看堆栈信息


图19

所以综上所述,该情况下的分类的数据加载时机的反推路径为:attachCategories -> load_categories_nolock -> loadAllCategories -> load_images

而我们的分类加载正常的流程的路径为:realizeClassWithoutSwift -> methodizeClass -> attachToClass ->attachCategories

其中正向和反向的流程如下图所示:

图20

你可能感兴趣的:(iOS底层-分类的加载原理)