1、往期文章
iOS 底层探索文章系列
上一篇中,我们分析了 类是如何加载到内存中的,那么今天我们探索的是,分类是如何加载到类里面的,以及分类和类搭配使用的情况。
2、探索分类的本质
我们用以下三种方式来探索分类的本质是什么
2.1 通过 Xcode Documentation
在 Documentation
搜索 Category
关键字
2.2 通过 objc 源码搜索 category_t
2.3 通过 clang
在终端输入 clang -rewrite-objc main.m -o main.cpp
,然后查看生成的c++文件
在源码中,我们已经得知分类的类型为 catagory_t
然后我们发现分类的倒数第二个的值为0,说明没有协议,所以被赋值为0。继续搜索 _CATEGORY_INSTANCE_METHODS_LGPerson_
,找到其底层实现
其中有一个方法,格式为:sel+签名+函数地址
。这个就是我们前面分析过的 method_t
的结构,我们在 objc 源码里面搜索 method_t
。
发现跟我们上面所说的一一对应了
name = sel
types = 签名
imp = 函数地址
同时,我们也注意到一个问题,在分类中,我们定义了两个属性,但是编译成c++之后,并没有看到相关属性,这是因为 分类中定义的属性没有相应的set和get方法的实现,只有方法的声明,后续我们将在关联对象的篇章中进行分析。
2.4 总结
综上所述,分类的本质就是一个 category_t
的结构体类型
3、分类的加载
我们先给 LGPerson 创建两个分类 LGA 和 LGB
在上一篇文章中,我们查看 methodizeClass
的实现时,可以发现 类的数据
和 分类的数据
是分开处理的,主要原因是因为在 编译阶段
,就已经确定好了方法的归属位置,而分类是后面才加进来的。
其中分类需要通过 attachLists
方法添加到类之后,外界才能进行试验,在此过程,我们已经知道了分类加载三个步骤中的后面两个步骤
- 分类数据
加载时机
: 根据 类和分类是否实现 load 方法 来区分不同的时机。 - 在
attachCategories
方法中准备分类数据 - 在
attachLists
中将分类数据添加到主类
4、分类的加载时机
下面我们来探索 分类数据的加载时机,以主类和两个分类实现 load
方法为例
4.1 通过第二步反推第一步的加载时机
通过上一篇文章我们了解到,当在走到 attachCategories
方法时,必然会有分类数据的加载,可以通过反推法查看 在什么时候调用 attachCategories
的,通过查找,有两个方法中调用
- 在
load_categories_nolock
方法中
- 在
attachToClass
方法中,这里经过调试发现,基本不会进到if流程中,除非加载两次,一般的类一般只会加载一次
我们直接运行 objc
代码,可以得出以下打印日志,通过输出日志可以发现 attachToClass
方法的下一步就是 load_categories_nolock
方法,进行加载分类数据
我们全局搜索 load_categories_nolock
的调用的地方,发现只有两次调用
一次是在 loadAllCategories
方法
另外一次是在 _read_images
方法,但是经过调试,是不会进入这个if判断流程的,而是走的 loadAllCategories
最后,我们全局搜索 loadAllCategories
在哪里地方进行了调用,发现只有 load_images
方法里面进行了调用
4.2 通过堆栈信息分析
在 attachCategories
中加自定义逻辑的断点,bt
查看堆栈信息。
可得,在上述情况下分类的数据加载时机为 attachCategories -> load_categories_nolock -> loadAllCategories -> load_images
,而我们分类的正常加载流程为 realizeClassWithoutSwift -> methodizeClass -> attachToClass ->attachCategories
我们看另外一种情况,主类和分类(LGA)实现 load
,分类(LGB)不实现
可以看到,只要有一个分类是非懒加载分类,那么所有的分类都会被标记位非懒加载分类。
5、类和分类的搭配使用
通过上面的例子,我们可以将类和分类是否实现 load
方法分为四种情况
类和分类 | 分类实现 load | 分类未实现 load |
---|---|---|
类实现 load | 非懒加载类 + 非懒加载分类 | 非懒加载类 + 懒加载分类 |
类未实现 load | 懒加载类 + 非懒加载分类 | 懒加载类 + 懒加载分类 |
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
是二维数组。
5.2 非懒加载类 + 懒加载分类
- 类和分类的加载在
read_images
就加载好数据了 - 其中
data
数据在编译期就已经完成了
5.3 懒加载类 + 非懒加载分类
- 此情况下会迫使主类提前加载,即主类强行转换为 非懒加载类样式。
5.4 懒加载类 + 懒加载分类
- 此情况下懒加载类与懒加载分类的数据加载是在 消息第一次调用时加载
5.5 总结
- 1. 非懒加载类 + 非懒加载分类,其数据的加载在
load_images
方法中,首先对类进行加载,然后把分类的信息贴到类中。 - 2. 非懒加载类 + 懒加载分类,其数据加载在
read_image
就加载数据,数据来自data,data在编译时期就已经完成,即data中除了类的数据,还有分类的数据,与类绑定在一起。 - 3. 懒加载类 + 懒加载分类,其数据加载推迟到 第一次消息时,数据同样来自data,data在编译时期就已经完成。
- 4. 懒加载类 + 非懒加载分类,只要分类实现了load,会迫使主类提前加载,即在
_read_images
中不会对类做实现操作,需要在load_images
方法中触发类的数据加载,即rwe初始化,同时加载分类数据。