【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加载时就调用,无论你用不用它都会调用
【OC底层】Category、+load方法、+initialize方法原理的更多相关文章
- 细说OC中的load和initialize方法
OC中有两个特殊的类方法,分别是load和initialize.本文总结一下这两个方法的区别于联系.使用场景和注意事项.Demo可以在我的Github上找到--load和initialize,如果觉得 ...
- NSObject的load和initialize方法(转)
全文转载自:http://www.cocoachina.com/ios/20150104/10826.html 在Objective-C中,NSObject是根类,而NSObject.h的头文件中前两 ...
- load和initialize方法
一.load 方法什么时候调用: 在main方法还没执行的时候 就会 加载所有类,调用所有类的load方法. load方法是线程安全的,它使用了锁,我们应该避免线程阻塞在load方法. 在项目中使 ...
- 解读OC中的load和initialize
在 Objective-C 中,NSObject 是绝大多数类的基类.而在 NSObject 中有两个类方法 load 和 initialize,那这两个方法是在什么时机被调用呢?父类.Categor ...
- +load和+initialize方法调用时机
一.+load方法什么时候调用 +load方法会在runtime加载类.分类时调用(程序运行起来会先去加载调用+load 跟你引用没有引用其头文件没有关系).每个类.分类的+load,在程序运行过程中 ...
- +Load和+initialize方法解析
http://www.cnblogs.com/ider/archive/2012/09/29/objective_c_load_vs_initialize.html
- oc---类方法load和initialize的区别
在iOS开发中,就像Application有生命周期回调方法一样,在Objective-C的类被加载和初始化的时候,也可以收到方法回调,可以在适当的情况下做一些定制处理.而这正是本篇文章所要介绍的lo ...
- Category、load、initialize 源码讲解
今天深圳天气有暴风雨,没有事情干,趁着周末和平常晚上写一篇关于Category知识的梳理!可能针对平常只会知道些category基本结论知道的人有些帮助,写这篇博客会按照下面的目录结合实例以及Cate ...
- iOS---Objective-C: +load vs +initialize
在 NSObject 类中有两个非常特殊的类方法 +load 和 +initialize ,用于类的初始化.这两个看似非常简单的类方法在许多方面会让人感到困惑,比如: 子类.父类.分类中的相应方法什么 ...
- +load 和 +initialize
APP 启动到执行 main 函数之前,程序就执行了很多代码. 执行顺序: 将程序依赖的动态链接库加载到内存 加载可执行文件中的所有符号,代码 runtime 解析被编译的符号代码 遍历所有的 cla ...
随机推荐
- YOLO object detection with OpenCV
Click here to download the source code to this post. In this tutorial, you’ll learn how to use the Y ...
- 一步一步 Pwn RouterOS之ctf题练手
前言 本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 本文目的是以一道比较简单的 ctf 的练手,为后面的分析 Rout ...
- windows 删除删除不掉的文件
DEL /F /A /Q \\?\%1RD /S /Q \\?\%1 windows下删除删除不掉的文件: 1.打开记事本,把上面的命令复制进去 2.保存,后缀名改为.bat,ok 3.把想要删除的文 ...
- 普通用户查看Oracle参数的值
create or replace function get_param(p_name in varchar2)return varchar2as l_param_type number; l_in ...
- linux centos5.8装yum安装mysql
默认的yum安装mysql都是5.1版本的 想要安装5.7的可以进行配置rpm包进行, mysql5.7安装路径 下面是默认的5.1安装路径 首先我们在使用yum安装的的时候会默认使用最新安装的,最 ...
- SOAR平台初探(一)
1.前言 Security Orchestration, Automation and Response(SOAR)安全编排和自动化响应,是Gartner2017年提出的新概念.Gartner预计到2 ...
- Mysql 安装服务无法启动解决方案与使用的一般使用指令
问题描述: 在安装mysql 时,如果第一次没安装成功,第二次重装可能出现莫名奇妙的情况.通过一番尝试,发现,安装往上的教程还是无法成功安装的主要原因是第一次安装虽然未成功,但是mysql 默认的33 ...
- npm install --save 和 --save-dev的区别
--save 会把依赖包名称添加到 package.json 文件 "dependencies" 键下--save-dev 则添加到 package.json 文件 "d ...
- 【ansible】Windows开启远程控制错误解决方案:无法检查防火墙状态
这个在老版本的Windows系统才有这种bug.例如Windows 2008 R2和Windows 7,如果你的系统的阿里云的Windows server 2008 R2,无需装这个,好像阿里云的系统 ...
- HTML5 canvas画图
HTML5 canvas画图 HTML5 <canvas> 标签用于绘制图像(通过脚本,通常是 JavaScript).不过,<canvas> 元素本身并没有绘制能力(它仅仅是 ...