分类的本质
在main中定义LGperson的分类LG
探索分类的本质,有以下三种方式
【方式一】通过clang
【方式二】通过Xcode文档搜索Category
【方式三】通过objc源码搜索 category_t
方式一:通过clang
【方式一】clang -rewrite-objc main.m -o main.cpp 查看底层编译,即 main.cpp,
其中分类的 类型是_category_t
分类的倒数第二个0,表示的是没有协议,所以赋值为0
搜索struct _category_t,如下所示
其中有两个method_list_t,分别表示实例方法 和 类方法
搜索CATEGORY_INSTANCE_METHODS_LGPerson,找到其底层实现
其中有3个方法,格式为:sel+签名+地址,是method_t结构体的属性即key
搜索method_t,其中对应关系如下
name 对应 sel
type 对应 方法签名
imp 对应 函数地址
方式二:通过Xcode文档搜索 Category
如果不会clang,可以通过Xcode文档搜索 Category
方式三:通过objc源码搜索 category_t
还可以通过objc源码搜索category_t类型
总结
综上所述,分类的本质 是一个_category_t类型
有两个属性:name(类的名称) 和 cls(类对象)
有两个 method_list_t类型的方法列表,表示分类中实现的实例方法+类方法
一个protocol_list_t类型的协议列表,表示分类中实现的协议
一个prop_list_t类型的属性列表,表示分类中定义的属性,一般在分类中添加的属性都是通过关联对象来实现
需要注意的是,分类中的属性是没有set、get方法
分类的加载
前提:创建LGPerson的两个分类: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
其中正向和反向的流程如下图所示: