Category 原理
- Category 编译之后的底层结构是 struct categroy_t, 里面存储着分类对象方法, 属性, 协议信息
- 当程序运行时, 通过 runtime 动态的将分类的方法, 属性, 协议合并到一个大数组中
- 底层使用的是二维数组进行存储, 比如:[[分类 2 方法列表],[分类 1 方法列表],[原方法列表]]
- 将合并后的分类数据 (方法, 属性, 协议) 的数组插入到类原来数据的前面, 如上
- 因为它遍历分类是按倒序遍历的, 所有越后面参与编译的 Category 数据, 会在数组的前面
源码的的 categroy_t 定义:
下面是 runtime 源码中其中一段代码, 用来处理分类与原类数据合并的:
- 源码解读顺序
- objc-os.mm
- _objc_init
- map_images
- map_images_nolock
- objc-runtime-new.mm
- _read_images
- remethodizeClass
- attachCategories
- attachLists
- realloc,memmove, memcpy
Category 与 Class Extension(类扩展)的区别:
- 类扩展是在编译时, 就会将方法, 属性, 协议全合并到一个类文件中
- 而 Category 是在运行时, 使用 runtime 动态的将数据合并到类信息中
+load 方法源码分析
下面是 load 方法其中一部分源码:
看代码可以看出, 确实是先调用类的 load 方法, 再调用分类的 load 方法, 我们看下类的 load 方法是如果调用的, 如下:
其中:(*load_method)(cls, SEL_load); 就是使用指针方式直接调用 load 方法, 不走 objc_msgSend 方法
分类的 load 方法调用和上面一样, 源码如下:
如果大家也想去看源码的话, 下面是源码跟踪顺序, 可以了解下:
+load 方法底层实现
调用时机:
+load 方法会在 runtime 加载类, 分类时调用
每个类, 分类的 + load, 在程序运行过程中只调用一次
调用顺序:
1, 先执行父类中的 load 方法
2, 先执行原类中的 load 方法
3, 再执行分类中的 load 方法, 按着编译的反顺序, 越后编译越先被执行
注意点:
当有多个分类时, 每个分类都重写原类中的一个方法时, 那程序调用这个方法的时候就会按编译文件的顺序来判断, 谁在最后就调用谁(可以通过项目设置中的 Build Phases-->Compile Sources 中调整)
分类中的方法不会覆盖原类中的方法, 只是把方法放在了原类方法之前, 通过 objc_msgSend 方法调用方法都是找到第一个就调用的
原理: 是将分类中的方法加入到了之前对象方法列表数组的前面了, 所有找方法的时候会先找到分类中的方法
+load 方法实例
创建两个类, 一个父类, 一个子类, 再分别创建 2 个父类的分类, 2 个子类的分类, 如下:
其中 XGPerson 是父类, XGStudent 是子类, 每个类里面都重写 load, 如:
XGStudent 也一样
直接运行程序, 看日志输出如下:
可以看出确实是先调用了父类的 load 再调用子类 load, 然后再调用分类的 load, 那这个分类中的 load 方法的顺序是怎么样的? 上面已经说过了, 就是参与编译的顺序, 如下:
+initialize 源码分析
上面的代码就是 initialize 源码的实现, 注释已经写的很清楚了, 这里主要是递归去处理父类
下面这个代码和上面是同一个方法里面的, 下面这个才是真正的去调用 initialize 的方法
下面去看下这个 callInitialize 的实现:
代码很简单, 直接就是使用的 objc_msgSend 的方法调用
下面是源码解读的顺序:
+initialize 方法实现
调用时机:
类在第一次接到的消息的时候调用, 每一个类只会 initialize 一次, 如:[XGPerson alloc], 就会调用一次, 并且后面再 alloc 也不会调用
调用顺序:
1, 先调用父类的 initialize
2, 再调用原类的 initialize(如果原类有分类, 并且分类重写 initialize, 则会调用分类中的 initialize, 当子类没有 initialize, 父类可能被调用多次)
按着编译的反顺序, 越后编译越先被执行
注意点:
当第一次调用子类的方法时, 会去判断是否有父类, 并且父类有没有调用过 initialize,
如果没有, 则先调用父类的, 再调用子类的
+initialize 方法实例
同样使用上面的那 2 个类和 4 个分类:
分别重写 initialize 方法, 和上面一样, 就不一一截图了:
1. 我们使用父类看下会输出什么:
输出日志:
可以看到这里调用的是 XGPerson 的 Play 的分类, 为什么为调用分类的这个方法呢? 上面说分类原理的时候也说到了, 分类方法和原类方法合并的时候会将分类的方法插入到原类方法之前, 只要通过 objc_msgSend 方式调用方法, 就会去这个列表最里找最先一个找到的方法进行调用. 因为刚才我们看原码也知道了 initialize 使用的就是 objc_msgSend 的方式调用方法的, 所以上面这个就会调用分类中的 initialize 方法.
2. 我们再来看下, 如果使用子类会怎么样:
输出:
结果也不难理解, 上面源码里也看到了, 会去先调用父类, 再去调用子类, 用的是那个递归方式.
3. 如果子类和子类的所有分类没有重写 initialize 方法, 那又会怎么样? 我们把子类和子类的所有分类的 initialize 方法给注释掉的输出结果:
从结果中可以看出, 当子类没有这个方法时, 它就会去父类中找这个方法, 所以父类的 initialize 会被调用多次, 通过 ISA 指针去找的, 之前有说过
+initialize 和 + load 的区别
+initialize 是通过 objc_msgSend 进行调用的, 所以有以下特点:
- 如果子类没有实现 + initialize, 会调用父类的 + initialize(所以父类的 + initialize 可能会被调用多次)
- 如果分类实现了 + initialize, 就覆盖类本身的 + initialize 调用, 也不能说是真正的覆盖, 只不会是放到原类方法的前面去了
- 第一次用的时候才会调用
+load 是直接通过指针调用的, 是在 runtime 加载时就调用, 无论你用不用它都会调用
来源: https://www.cnblogs.com/xgao/p/9963493.html