【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 ...
随机推荐
- JavaWEB SSH文件上传
一.提交表单的<form> method属性必须为post 并且添加enctype="multipart/form-data" 属性 前台: <td>上传 ...
- 让浏览器识别HTML5规范中的新标签
IE8浏览器中还没有添加对HTML5新标签的支持,所以在IE8中无法直接展现HTML5新标签中的内容.庆幸的是IE8/IE7/IE6支持通过document.createElement方法产生的标签, ...
- Spring Boot—06集成前端模板thymeleaf
Spring Boot建议使用这些模板引擎,避免使用JSP,若一定要使用JSP将无法实现Spring Boot的多种特性 pom.xml <dependency> <groupId& ...
- linux中文字体
◆ 背景说明 报表,在windows下,展现.导出都正常,在linux下,字体变大了.比如,单元格的大小设计好后,里面的字当好能一行显示完,将报表放到linux下后,字变大了,一行显示不完了,变 ...
- leetcode题解之分解字符串域名
1.题目描述 A website domain like "discuss.leetcode.com" consists of various subdomains. At the ...
- spring boot(16)-mail发邮件
上一篇讲了如何处理异常,并且异常最终会写入日志.但是日志是写在服务器上的,我们无法及时知道.如果能够将异常发送到邮箱,我们可以在第一时间发现这个异常.当然,除此以外,还可以用来给用户发验证码以及各种离 ...
- maven问题总结
1.maven下载jar包速度慢 1.maven下载jar包速度慢(解决办法) 现在maven项目非常流行,因为它对jar实行了一个非常方便的管理,我们可以通过在pom.xml文件中做对应的配置即可将 ...
- Oracle EBS PO退货失败
无法读取例程 &ROUTINE 中配置文件选项 INV_DEBUG_TRACE 的值. 系统-配置文件-地点层 INC%调试%踪 是 select * from po_interface_e ...
- MySQL案例07:MySQL5.7并发复制隐式bug
我们MySQL线上环境大部分使用的是5.7.18的版本,这个版本已修复了很多bug,但针对主从复制的bug还是有很多的,尤其是一些组复制.并行复制的bug尤为突出,在5.7.19版本有做相应改善和修复 ...
- 纲举目张:打通MySQL架构和业务的任督二脉
目前,在很多OLTP场景中,MySQL数据库都有着广泛的应用,也有很多不同的使用方式.从数据库的业务需求.架构设计.运营维护.再到扩容迁移,不同的MySQL架构有不同的特点,适应一定的业务场景,或者解 ...