【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加载时就调用,无论你用不用它都会调用

原文地址:https://www.cnblogs.com/xgao/p/9963493.html

时间: 2024-11-07 10:25:45

【OC底层】Category、+load方法、+initialize方法原理的相关文章

细说OC中的load和initialize方法

OC中有两个特殊的类方法,分别是load和initialize.本文总结一下这两个方法的区别于联系.使用场景和注意事项.Demo可以在我的Github上找到——load和initialize,如果觉得有帮助还望点个star以示支持,总结在文章末尾. load 顾名思义,load方法在这个文件被程序装载时调用.只要是在Compile Sources中出现的文件总是会被装载,这与这个类是否被用到无关,因此load方法总是在main函数之前调用. 调用规则 如果一个类实现了load方法,在调用这个方法

NSObject的load和initialize方法(转)

全文转载自:http://www.cocoachina.com/ios/20150104/10826.html 在Objective-C中,NSObject是根类,而NSObject.h的头文件中前两个方法就是load和initialize两个类方法,本篇文章就对这两个方法做下说明和整理. 1.概述 Objective-C作为一门面向对象语言,有类和对象的概念.编译后,类相关的数据结构会保留在目标文件中,在运行时得到解析和使用. 在应用程序运行起来的时候,类的信息会有加载和初始化过程.其实在Ja

load和initialize方法

  一.load 方法什么时候调用: 在main方法还没执行的时候 就会 加载所有类,调用所有类的load方法. load方法是线程安全的,它使用了锁,我们应该避免线程阻塞在load方法. 在项目中使用的一些场景: 比如我们要统计所有页面(UIViewController.UITableViewController)的数据,可以在UIViewController的类别里的load方法里实现Method Swizzle @implementation UIViewController (BaseM

Load与initialize方法

为了讲清楚饿汉式单例模式实现需要了解一下这两个方法.它们的特别之处,在于iOS会在运行期提前并且自动调用这两个方法,而且很多对于类方法的规则(比如继承,类别(Category))都有不同的处理因为这两个方法是在程序运行一开始就被调用的方法,我们可以利用他们在类被使用前,做一些预处理工作.比如我碰到的就是让类自动将自身类名保存到一个NSDictionary中.Apple的文档就不再这里给出了,可以自己去看看. Apple的文档很清楚地说明了initialize和load的区别在于:load是只要类

NSObject的load和initialize方法

load和initialize的共同特点 在不考虑开发者主动使用的情况下,系统最多会调用一次 如果父类和子类都被调用,父类的调用一定在子类之前 都是为了应用运行提前创建合适的运行环境 在使用时都不要过重地依赖于这两个方法,除非真正必要 load和initialize的区别 load方法 调用时机比较早,运行环境有不确定因素.具体说来,在iOS上通常就是App启动时进行加载,但当load调用的时候,并不能保证所有类都加载完成且可用,必要时还要自己负责做auto release处理.对于有依赖关系的

iOS开发-OC篇 load方法 和 initialize方法比较

Load方法 和 initialize方法的比较    在OC语言中,我们相比之下对于load和initialize方法的使用比较少,所以会不是很清楚的了解二者的用途和区别,所以整理了一下,和大家进行分享,有所得不对的地方,希望能够指出来,多谢! 1.load方法特点: 1> 当类被引用进程序的时候会执行这个函数 2> 一个类的load方法不用写明[super load],父类就会收到调用,并且在子类之前. 3> Category的load也会收到调用,但顺序上在主类的load调用之后.

iOS load方法与initialize方法

在 iOS 开发中,我们经常会使用 +load 方法来做一些在 main 函数之前的操作,比如方法交换(Method Swizzle)等.现在分析一下load方法跟initialize方法的调用顺序以及区别. 1.先看下load方法 尝试定义一个继承自 NSObject 的 Person 类,并对其添加两个分类 Life 和 Work:再定义一个 Student 类继承自 Person,并对其添加 School 分类.在以上所有类和分类中,均实现 +load: #import "Person.h

Objective-C类方法 load 和 initialize

1.区别:+load 是只要类所在文件被引用就会被调用,而 +initialize 是在类或者其子类的第一个方法被调用前调用.所以如果类没有被引用进项目,就不会有 +load 调用:但即使类文件被引用进来,如果没有使用,那么 +initialize 也不会被调用. 2.相同点:方法只会被调用一次. 3.+load 方法探讨 结论 :+load 的执行顺序是先父类 再到子类,后 category,而 category 的 +load 执行顺序是根据编译顺序决定的. 4.+initialize 方法

+load 和 +initialize

APP 启动到执行 main 函数之前,程序就执行了很多代码. 执行顺序: 将程序依赖的动态链接库加载到内存 加载可执行文件中的所有符号,代码 runtime 解析被编译的符号代码 遍历所有的 class 按继承层级一次调用 Class 的 load 和 category 的 load 方法. +(void)initialize 与 +(void)load 两个方法的比较 +load +initialize 调用时机 被添加 runtime 时 收到第一条消息时,也可能永远不调用 调用顺序 父类

iOS load和initialize的区别

可能有些还不清楚load和initialize的区别,下面简单说一下: 首先说一下 + initialize 方法:苹果官方对这个方法有这样的一段描述:这个方法会在 第一次初始化这个类之前 被调用,我们用它来初始化静态变量. initialize方法的调用时机,当向该类发送第一个消息(一般是类消息首先调用,常见的是alloc)的时候,先调用类中的,再调用类别中的(类别中如果有重写):如果该类只是引用,没有调用,则不会执行initialize方法.两者方法的共同点:自动调用父类的,不需要super