【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 ...
随机推荐
- dojox.grid.DataGrid显示数据的方法(转)
第一种:数据存在本地或者已经写死的JSON对象中,不需要跟服务端进行数据传输 <%@ page language="java" contentType="text/ ...
- Unexpected token o in JSON at position 1 at JSON.parse (<anonymous>) SyntaxError: Unexpected token R in JSON at position 0 at JSON.parse (<anonymous>)
这个问题在之前做项目时碰到过一次,当时按照网上的做法,去掉JSON.parse()这一层转换后就没有这个报错了,数据也能正常使用,就没多想,也没深究是什么原因.可是这次又碰到了,所以这次我必须要弄明白 ...
- Spring Boot—05页面跳转
package com.smartmap.sample.ch1.controller.view; import org.springframework.stereotype.Controller; i ...
- apache ftp server的简单入门(java应用内嵌ftp server)
Apache Ftp Server:(强调) Apache Ftp Server 是100%纯Java的FTP服务器软件,它采用MINA网络框架开发具有非常好的性能.Apache FtpServer ...
- 转 Js窗体window大小设置
网页可见区域宽:document.body.clientWidth 网页可见区域高:document.body.clientHeight 网页可见区域宽:document.body.offsetW ...
- [Asp.net mvc]Asp.net mvc 中使用LocalStorage
目前使用比较多的本地存储方案有比如Flash SharedObject.Google Gears.Cookie.LocalStorage.User Data.Open Database等方案.综合比较 ...
- mysql group replication观点及实践
一:个人看法 Mysql Group Replication 随着5.7发布3年了.作为技术爱好者.mgr 是继 oracle database rac 之后. 又一个“真正” 的群集,怎么做到“ ...
- [微信] 客服接口调用的时候返回 40003 Invalid OpenID
首先确认收件人在24小时内主动向公众号发过消息.该消息的 FromUserId 即是客服消息的 touser 参数的 OpenId 2017-05-19 更新:可以使用UTF-8了 string ur ...
- TreeView控件概述、属性与方法
1.作用:用于显示Node结点的分层列表.2.添加到控件箱菜单命令:工程 | 部件,在部件对话框中选择:Microsoft Windows Common Controls 6.03.TreeView控 ...
- Ubuntu做Tomcat服务:insserv: warning: script 'tomcat' missing LSB tags and overrides
https://blog.csdn.net/hanchao5272/article/details/79819460 转载自:https://blog.bbzhh.com/index.php/arch ...