【OC底层】Category、+load方法、+initialize方法原理
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加载时就调用,无论你用不用它都会调用
上一篇: Oracle删除归档文件