【OC底层】Category、+load方法、+initialize方法原理

  Category原理

- Category编译以后的底层结构是 struct categroy_t,里面存储着分类对象方法、属性、协议信息
- 当程序运行时,经过runtime动态的将分类的方法、属性、协议合并到一个大数组中
- 底层使用的是二维数组进行存储,好比:[[分类2方法列表],[分类1方法列表],[原方法列表]]
- 将合并后的分类数据(方法、属性、协议)的数组插入到类原来数据的前面,如上
- 由于它遍历分类是按倒序遍历的,全部越后面参与编译的Category数据,会在数组的前面数组

 源码的的 categroy_t 定义:源码分析

 

下面是runtime源码中其中一段代码,用来处理分类与原类数据合并的:ui

 

- 源码解读顺序
objc-os.mm
_objc_init
map_images
map_images_nolockspa

objc-runtime-new.mm
_read_images
remethodizeClass
attachCategories
attachLists
realloc、memmove、 memcpy 3d

 

Category与Class Extension(类扩展)的区别:

- 类扩展是在编译时,就会将方法、属性、协议全合并到一个类文件中,不能为系统类添加扩展
- 而Category是在运行时,使用runtime动态的将数据合并到类信息中,能够为系统类添加分类指针

 

+load方法源码分析

下面是load方法其中一部分源码:日志

 

 

调用全部load方法对象

 

 

 

看代码能够看出,确实是先调用类的load方法,再调用分类的load方法,咱们看下类的load方法是如何调用的,以下:blog

 

其中:(*load_method)(cls, SEL_load); 就是使用指针方式直接调用load方法,不走 objc_msgSend方法递归

分类的load方法调用和上面同样,源码以下:

 

若是你们也想去看源码的话,下面是源码跟踪顺序,能够了解下:

 

+load方法底层实现

调用时机:

+load方法会在runtime加载类、分类时调用

每一个类、分类的+load,在程序运行过程当中只调用一次

 

调用顺序:

一、先执行父类中的load方法
二、先执行原类中的load方法
三、再执行分类中的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 也不会调用

 
调用顺序:

一、先调用父类的initialize
二、再调用原类的initialize(若是原类有分类,而且分类重写initialize,则会调用分类中的initialize,当子类没有initialize,父类可能被调用屡次)
   按着编译的反顺序,越后编译越先被执行

 

注意点:

当第一次调用子类的方法时,会去判断是否有父类,而且父类有没有调用过initialize,
若是没有,则先调用父类的,再调用子类的

 

+initialize方法实例

一样使用上面的那2个类和4个分类:

分别重写initialize方法,和上面同样,就不一一截图了:

 

1. 咱们使用父类看下会输出什么:

输出日志:

 能够看到这里调用的是XGPersonPlay的分类,为何为调用分类的这个方法呢?上面说分类原理的时候也说到了,分类方法和原类方法合并的时候会将分类的方法插入到原类方法以前,只要经过objc_msgSend 方式调用方法,就会去这个列表最里找最早一个找到的方法进行调用。由于刚才咱们看原码也知道了 initialize 使用的就是 objc_msgSend 的方式调用方法的,因此上面这个就会调用分类中的 initialize 方法。

 

2. 咱们再来看下,若是使用子类会怎么样:

 输出:

结果也不难理解,上面源码里也看到了,会去先调用父类,再去调用子类,用的是那个递归方式。

 

3. 若是子类和子类的全部分类没有重写 initialize 方法,那又会怎么样?咱们把子类和子类的全部分类的 initialize 方法给注释掉的输出结果:

从结果中能够看出,当子类没有这个方法时,它就会去父类中找这个方法,因此父类的initialize会被调用屡次,经过ISA指针去找的,以前有说过

 

+initialize和+load的区别

+initialize 是经过objc_msgSend进行调用的,因此有如下特色:
 - 若是子类没有实现+initialize,会调用父类的+initialize(因此父类的+initialize可能会被调用屡次)
 - 若是分类实现了+initialize,就覆盖类自己的+initialize调用,也不能说是真正的覆盖,只不过是放到原类方法的前面去了

 - 第一次用的时候才会调用

+load 是直接经过指针调用的,是在runtime加载时就调用,不管你用不用它都会调用

相关文章
相关标签/搜索