OC文件在编译后,类相关的数据结构会保留在目标文件中,在运行时获得解析和使用。在应用程序运行起来的时候,类的信息会有加载和初始化过程,这个过程就涉及到了类的两个类方法:load
和initialize
。下面咱们就来介绍一下这2个方法的区别。(首先要说明一下,这2个方法是系统调用的,开发者通常不会主动去调用者两个方法,这么作也没有什么意义,因此后面的讲解都是针对系统调用,不考虑主动调用的状况)。安全
当咱们启动程序时,参与了编译的类、分类都会被加载进内存,load
方法就是在这个类被加载的时候调用的(前提是这个类有实现load
方法),这个过程与这个类是否被使用是无关的,也就是说若是有一个类(MyClass)即便在整个程序中都没有用到,甚至没有任何一个文件去引入MyClass的头文件,MyClass的的load
的方法同样会被调用。等全部的类、分类都加载进内存后才会调用程序的main
函数,因此全部类的load
方法都是在main
函数以前被调用的。并且每一个类、分类的load
方法只会被调用一次。bash
一个程序中若是全部的类、分类都实现了load
方法,那么全部的load
方法都会被调用。它们的执行顺序遵循如下规则:数据结构
load
方法,再执行全部分类的load
方法。load
方法时,是按照参与编译的顺序,先编译的类先执行,可是若是某个类是继承自另外一个类,那么会先执行父类的load
方法个再执行本身的load
方法。load
方法时,是按照分类参与编译的顺序,先编译的分类先执行。关于编译顺序,咱们能够在项目的Build Phases
--> Compile Sources
查看,最上面的就最早编译,咱们能够拖动文件来调整编译顺序。app
下面举个例子来看下load
方法的执行顺序。首先说明一下几个类的关系:Person
类有aaa
和bbb
两个分类,men
类继承自Person
类,men
也有2个分类ccc
和ddd
,Book
类和前面这些类没有任何关系。函数
load
再执行分类的load
,最早参与编译的类是men
,而men
继承自Person
,因此最早执行Person
的load
(虽然Person
是后参与编译的,可是它是父类,因此会先执行),而后再执行men
的load
。接着参与编译的是Book
类,因此紧接着就是执行Book
的load
。再接着参与编译的类就是Person
,因为它的load
方法已经执行过了,此时就不会执行了。load
方法都执行完后开始执行分类的load
,分类参与编译的顺序是men+ccc
-->Person+aaa
-->men+ddd
-->Person+bbb
,因此分类的load
方法个也是按照这个顺序执行。咱们知道,当分类中存在和本类中同名的方法时,调用这个方法最终执行的是分类中的方法。那上面就很奇怪了,Person
和Person的分类
中都有load
方法,按理说调用load
方法时最终只会调用其中一个分类的load
方法,可结果Person
本类和它的2个分类都调用了load
方法。ui
这是由于load
方法和普通方法调用的方式不同。普通方法调用是经过消息发送机制实现的,会先去类或元类的方法列表中查找,若是找到了方法就执行,若是没有找到就去父类的方法列表里面找,只要找到就会终止查找,因此只会执行一次。spa
而load
方法调用时,每一个类都是根据load
方法的地址直接调用,而不会走objc_msgSend
函数的方法查找流程,也就是说一个类有实现load
方法就执行,没有就不执行(没有的话也不会去父类里面查找)。code
想要了解更加详细的底层实现流程,能够去看objc4源码,这里提供一下相关函数调用流程以便进行源码阅读: 首先从objc-os.mm
文件的_objc_init
函数开始-->load_images
-->prepare_load_methods
-->schedule_class_load
-->add_class_to_loadable_list
-->add_category_to_loadable_list
-->call_load_methods
-->call_class_loads
-->call_category_loads
-->(*load_method)(cls, SEL_load)
。cdn
咱们一般在load
方法中进行方法交换(Method Swizzle),除此以外,除非真的有必要,咱们尽可能不要在load
方法中写代码,尤为不要在load
方法中使用其它的类,由于这个时候其它的类可能尚未被加载进内存,随意使用可能会出问题。对象
若是确实要在load
方法写一些代码,那也要尽可能精简代码,不要作一些耗时或者等待锁的操做,由于整个程序在执行load
方法时都会阻塞,从而致使程序启动时间过长甚至没法启动。
initialize
方法是在类或它的子类收到第一条消息时被调用的,这里的消息就是指实例方法或类方法的调用,因此全部类的initialize
调用是在执行main
函数以后调用的。并且一个类只会调用一次initialize
方法。若是一个类在程序运行过程当中一直没有被使用过,那这个类的initialize
方法也就不会被调用,这一点和load
方法是不同的。
initialize
方法的调用和普通方法调用同样,也是走的objc_msgSend
流程。因此若是一个类和它的分类都实现了initialize
方法,那最终调用的会是分类中的方法。
若是子类和父类都实现了initialize
方法,那么会先调用父类的方法,而后调用子类的方法个(这里注意子类中不须要写[super initialize]
来调用父类的方法,经过查看源码得知它是在底层实现过程当中主动调用的父类的initialize
方法)。
下面看一个例子:
父类Person
实现了initialize
,PersonSub1
和PersonSub2
这两个子类也实现了initialize
,PersonSub3
和PersonSub4
这两个子类没有实现了initialize
,按照下面的顺序实例化对象:
PersonSub1 *ps1 = [[PersonSub1 alloc] init];
Person *person = [[Person alloc] init];
PersonSub2 *ps2 = [[PersonSub2 alloc] init];
PersonSub3 *ps3 = [[PersonSub3 alloc] init];
PersonSub4 *ps4 = [[PersonSub4 alloc] init];
// ***************打印结果***************
2020-01-06 15:52:38.429218+0800 CommandLine[68706:7207027] +[Person initialize]
2020-01-06 15:52:38.429250+0800 CommandLine[68706:7207027] +[PersonSub1 initialize]
2020-01-06 15:52:38.429287+0800 CommandLine[68706:7207027] +[PersonSub2 initialize]
2020-01-06 15:52:38.429347+0800 CommandLine[68706:7207027] +[Person initialize]
2020-01-06 15:52:38.429380+0800 CommandLine[68706:7207027] +[Person initialize]
复制代码
看到这个运行结果,有人就有疑问了:不是说一个类只会调用一次initialize
方法吗,为何这里Person
的initialize
方法被调用了3次?
这里就须要讲解一下底层源码的执行流程了,每一个类都有一个标记记录这个类是否调用过initialize
,我这里就用一个BOOL类型的isInitialized
来表示,而后用selfClass
来表示本身的类,用superClass
来表示父类,下面我用伪代码来描述一下底层源码执行流程:
// 若是本身没有调用过initialize就执行里面的代码
if(!selfClass.isInitialized){
if(!superClass.isInitialized){
// 若是父类没有执行过initialize就给父类发消息(一旦成功执行initialize就将父类的isInitialized置为YES)
objc_msgSend(superClass,@selector(initialize));
}
// 再给本身的类发消息(一旦成功执行initialize就将本身类的isInitialized置为YES)
objc_msgSend(selfClass,@selector(initialize));
}
复制代码
接下来我来按照这个流程来解释一下上面运行的结果:
PersonSub1
被使用,而此时PersonSub1
的isInitialized
为NO,并且父类Person
的isInitialized
也为NO,因此先给父类发消息执行initialize
,执行完后Person
的isInitialized
变为YES。而后PersonSub1
执行本身的initialize
,执行完后PersonSub1
的isInitialized
变为YES。因此这一步先打印+[Person initialize]
,而后打印+[PersonSub1 initialize]
。Person
实例化,此时Person
的isInitialized
为YES,因此不会再调用initialize
。因此这一步什么都没打印。PersonSub2
实例化,此时PersonSub2
的isInitialized
为NO,父类Person
的isInitialized
为YES,因此只有PersonSub2
会执行initialize
,执行完后PersonSub2
的isInitialized
变为YES。因此这一步打印的是+[PersonSub2 initialize]
。PersonSub3
实例化,此时PersonSub3
的isInitialized
为NO,父类Person
的isInitialized
为YES,因此只有PersonSub3
会执行initialize
,可是因为PersonSub3
没有实现initialize
,它就会去父类找这个方法的实现,找到后就执行父类Person
的initialize
(注意这里是PersonSub3
执行的Person
中的initialize
,而不是Person
执行的),执行完后PersonSub3
的isInitialized
变为YES。因此这一步打印的是+[Person initialize]
。(注意这里打印的是方法信息,表示执行的是Person
中的initialize
,而不是说是Person
调用的initialize
)。PersonSub4
实例化,这一步过程和上面一步是同样的,执行完后PersonSub4
的isInitialized
变为YES。这一步打印的是+[Person initialize]
。因此最后的结果就是Person
、PersonSub1
、PersonSub2
、PersonSub3
、PersonSub4
这5个类都执行了一次initialize
,虽然从运行结果来看Person
的initialize
执行了3次,其实后面2次是PersonSub3
和PersonSub4
调用的。
虽然使用initialize
要比使用load
安全(由于在调用initialize
时全部类已经被加载进内存了),但咱们仍是要尽可能少用initialize
这个方法个,尤为要谨慎在分类中实现initialize
方法,由于若是在分类中实现了,本类实现的initialize
方法将不会被调用。实际开发中initialize
方法通常用于初始化全局变量或静态变量。