load 和 initialize 两个方法算是两个特殊的类方法了,今天偶然从草稿箱中看到还有本篇未完成的博文,如果说当初了解它们是为了应付面试,那么工作之后,了解它们就变成了“必修课”,相比于网上某些十分官方的说法,个人还是喜欢用大白话来诠释自己对它们的理解,这里就写一下个人对这两个方法的理解,如果有理解不到位的地方,还请指正,3Q
load方法
个人理解
从方法的名字来看,它应该是在一个类进行装载的时候触发,更果断一点的说法就是不管这个类有没有被调用,只要它被装载,那么它就会运行这个方法。
那么它什么时候才会被装载呢?而这个时间点应该就是这个类文件在被第一次编译的时候.
个人理解的编译其实就是由编译器静态的分析语法等是否符合标准的过程并将符合标准的语法翻译成机器语言(这也就是我们当语法不对的时候会出现报错,这个阶段是静态的,如果这里有些值是需要动态确定的,强烈建议请换个地方初始化吧)。
示例
如果还是比较抽象,这里就用代码创建几个类来说明一下,以下创建一个叫做Person的类作为基类,在创建一个Student类继承自Person类,同时写一个Student的类别,并分别实现它们的load方法:
//Person.m
+(void)load { NSLog(@"I am Person..Load Function!"); }
//Student.m 继承自Person
+(void)load { NSLog(@"I am Student..Load Function!"); }
//Student+CustomLoad.m
+(void)load { NSLog(@"I am Student..Load Function!"); }
此时我们在main.m文件不做任何的操作,编译通过后运行,打印结果如下:
2016-08-30 14:36:59.037 Load_Initalization_Test[25633:319076] I am Person..Load Function!
2016-08-30 14:36:59.038 Load_Initalization_Test[25633:319076] I am Student..Load Function!
2016-08-30 14:36:59.038 Load_Initalization_Test[25633:319076] I am Student+CustomLoad..Load Function!
Program ended with exit code: 0
尽管我没有调用它们,但是它们load方法执行了,这就应了之前说的话,不管类有没有被调用,只要编译到就会执行load方法。
那么我们怎么知道文件会不会编译呢,其实在创建这个类的时候,Xcode自动已经帮我们把它添加到了Compile Sources里面(这里面的实现文件会在编译器编译阶段进行加载,也就是我们所说的load),具体位置就在Targets->Build Phases->Compile Source里面。
如下图(当然,如果把Student+CustomLoad.m从里面删掉,那么就不会打印Student+CustomLoad..Load Function!
这句了;但如果您想把Person.m去掉,那么编译器是不会让你通过,因为Student的load依赖于父类Person)
执行顺序
那么顺序为什么load方法会是Person优先,其次是Student,最后才是Student+CustomLoad呢,load的开发文档中有这么两句话直接的阐明了它们的调用顺序(尽管我相信大家能看得懂,但在后面还是简单的翻译了一下):
- A class’s +load method is called after all of its superclasses’ +load methods.
- 一个类的load方法是在它所有的父类之后执行
- A category +load method is called after the class’s own +load method.
- 一个类别的load方法是在自己的load方法之后执行
总结
- load方法不需要(不需要并不是说不能哦)使用[super load]来显性的调用父类的load方法,只要被添加到编译源下面就会执行。
- 不管子类有没有写load方法,父类的load都只会执行一次(说这句只是为了区别下面的initialize方法)
- load方法执行的时候,系统为脆弱状态,如果我们在load里面需要调用其它类的实例对象(或类对象)的属性或者方法,必须要确保那个依赖类(这个依赖类可不是之前说的父类)的load方法执行完毕(个人看法: 这种牵扯到稍微带点逻辑的东西就不要在里面实现了,使用场景↓。
使用场景
load方法是线程安全的,内部已经使用了锁,一般的应用场景是在该方法中实现方法的交换(Method Swizzle (runtime的黑魔法)),比如我们对Person和Student新增两个方法如下:
//Person.m
-(void)personSay { NSLog(@"I am a Person"); }
//Student.m
-(void)studentSay { NSLog(@"I am a Student"); }
//Student.m load方法里面对方法实现进行交换
+(void)load
{
//实现personSay与studentSay方法的交换
Method personMethod = class_getInstanceMethod([Person class], NSSelectorFromString(@"personSay"));
Method studentMethod = class_getInstanceMethod([Student class], NSSelectorFromString(@"studentSay"));
method_exchangeImplementations(personMethod, studentMethod);
}
//这个时候我们在main.m的函数里面使用student实例对象调用方法studentSay,打印结果为I am a Person
initialize方法
个人理解
这个方法是当某个类第一次收到消息的时候触发(如果稍微有一点点看过runtime的话,应该会知道ObjC中的调用方法本质就是消息的传递)。
以下是个人的理解,如果有错误请指出:
1、可以当成一个单例方法,只不过与之前所理解的单例方法稍有不同,这里初始化的不是一个实例对象,而是一个类对象,因为类对象其实就是一个单例对象嘛,我们可以释放实例对象,但谁能释放一个类对象给楼主瞧一瞧呢(可能这句话说的有点自大,也可能真的有办法,但只是为了好理解才这么说的,大神这里就不要挑刺,不过具体方法楼主还是很感兴趣滴,求告知呀)。
2、如果没有用到该类,就算加载完毕也不会执行该方法(这点与load方法不同,load方法是只要加载就执行,initialize方法必须是第一次使用该类的时候才触发,如果按照load的那个实例,那么不会执行initialize方法)
3、如果对类对象的理解有点模糊,那么就举个例子:如果说在调用实例方法(-)的时候必须要有一个实例对象,那么在调用类方法(+)的时候是不是也需要有一个类对象呢,比如alloc
以及new
,这两个类方法用的算是最多的了吧..如果没有初始化一个类对象,请调用一个我看看O(∩_∩)O
示例
如果上面的一系列文字更让人摸不着头脑的话,我觉得下面的示例代码会让思路更加清晰一下:
初始化父类对象并只实现父类的initialize方法
实现Person类的initialize初始化方法
//Person.m
+(void)initialize { NSLog(@"I am Person..initialize Function!"); }
这里与load方法不同,在main函数需要调用一下Person类,这里就打印了一下Person的类型
int main(int argc, const char * argv[]) {
@autoreleasepool {
//打印Person的类型,当然大家肯定知道他是Person类啦
NSLog(@"Person Class = %@",NSStringFromClass([Person class]));
}
return 0;
}
看一下结果吧:
//优先调用了initialize方法
2016-09-01 14:58:42.228 Load_Initalization_Test[5012:313370] I am Person..initialize Function!
2016-09-01 14:58:42.229 Load_Initalization_Test[5012:313370] Person Class = Person
按照个人的理解解释一下为什么会优先调用initialize方法的原因吧:
在main方法里我们调用了Person的一个类方法,没错,就是[Person class]
,有没有想到一句话呢?第一次使用类发送消息之前调用的方法,这里的调用class方法的时候是第一次使用Person类,所以调用了Person的initialize方法。
初始化子类对象并同时实现子类和父类的initialize方法
实现方法如下:
//Person.m
+(void)initialize { NSLog(@"I am Person..initialize Function!"); }
//Student.m 继承自Person类
+(void)initialize { NSLog(@"I am a Student..initialize Function!"); }
这里就不在main函数里面打印Person的类型了,我们打印一下Student的类型吧
//简单的一句打印
NSLog(@"Student Class = %@",NSStringFromClass([Student class]));
打印结果如下:
//这个时候发现Student的父类Person的initialize方法优先
2016-09-01 15:12:11.846 Load_Initalization_Test[5154:326709] I am Person..initialize Function!
//Student的initialize其次,至于为什么优先执行父类的方法,下面会有执行顺序的一个描述,这个实例不是重点
2016-09-01 15:12:11.847 Load_Initalization_Test[5154:326709] I am a Student..initialize Function!
//最后打印的才是Student的类型
2016-09-01 15:12:11.847 Load_Initalization_Test[5154:326709] Student Class = Student
初始化子类对象并只实现父类的initialize方法
代码就不贴了,只要把Student.m中的initialize完全注释掉即可,打印结果如下:
//父类的initialize方法执行了一次,意料之中
2016-09-01 15:16:40.307 Load_Initalization_Test[5222:331121] I am Person..initialize Function!
//我去,这是什么鬼,为啥又执行了一次
2016-09-01 15:16:40.308 Load_Initalization_Test[5222:331121] I am Person..initialize Function!
2016-09-01 15:16:40.308 Load_Initalization_Test[5222:331121] Person Class = Student
这也就是在总结load的时候有这么一句不管子类有没有写load方法,父类的load都只会执行一次
,而initialize的方法不同,如果子类的initialize没有实现,那么它就会继续执行一遍父类的initialize方法,为什么会出现这种情况,请看下面的执行顺序↓;如果的执行顺序会有什么问题呢,请看后面的使用场景。
执行顺序
下面是开发文档中节选的那么几段
- Superclasses receive this message before their subclasses.
- 父类会在子类之前收到这个消息
- The superclass implementation may be called multiple times if subclasses do not implement initialize
- 如果子类没有实现这个方法,那么父类的实现将会被执行数次
总结
- initialize方法也不需要使用[super initialize]来显性调用父类的initialize方法,只有第一次调用该类的时候才会触发。
- 如果子类实现了initialize方法,那么初始化时各自执行各自的initialize方法,如果子类没有实现initialize方法,那么就会自动调用父类的initialize方法。
- initialize方法也是在一个安全线程中,也不需要编写复杂逻辑的代码。
使用场景
runtime向这个类发送初始化消息的时候是线程安全的,所以也不需要在这个方法里面添加太复杂的逻辑,万一死锁呢,通常我们会在这里面对静态变量进行初始化,比如:
static NSString * personName;
@implementation Person
//实现
+(void)initialize
{
NSLog(@"I am Person..initialize Function!");
personName = @"RITL";
}
这样的话,如果调用Person子类的时候,没有写initialize方法,岂不是要对这个静态变量初始化N次,为了防止这种情况的发生,可以写成如下:
+(void)initialize
{
//判断一下当前的类型,当然不止这一种写法
//比如if([NSStringFromClass([self class]) isEqualToString:@"Person"])也是可以的
if (self == [Person self])
{
NSLog(@"I am Person..initialize Function!");
personName = @"RITL";
}
}
这个时候打印一下,结果如下:
//只执行了一次,解决初始化多次的问题
2016-09-01 15:31:39.973 Load_Initalization_Test[5415:348083] I am Person..initialize Function!
2016-09-01 15:31:39.974 Load_Initalization_Test[5415:348083] Person Class = Student
Super 关键词
这里为什么要追加一下super"关键词"
呢,注意,这里说super是一个关键词,而不是很多人理解的父类!!
我们知道的是,当使用super关键词调用方法的时候是不会执行本类的方法,而是调用父类的方法。下面的实例应该都是懂得,比如:
//调用本类的say方法
[self say];
//调用父类的say方法
[super say];
super的作用:使用super关键词调用方法的时候,在runtime查找方法的实现时不会从当前的类的方法列表中查找,而是跳过本类从父类的方法列表中查找实现方法。尽管执行的是父类的方法,但是方法的调用者(消息的发送者)依旧是当前类,这也就解释了下面这段代码
//在Student的initialize方法中如下调用
+(void)initialize
{
NSLog(@"super class = %@",NSStringFromClass([super class]));
NSLog(@"self class = %@",NSStringFromClass([self class]));
}
//打印结果(全是Student):
2016-09-02 09:01:23.737 Load_Initalization_Test[1259:49022] super class = Student
2016-09-02 09:01:23.737 Load_Initalization_Test[1259:49022] self class = Student
牛刀小试
小试
如果感觉自己理解了load和initialize方法的话,不如来测试一下吧,说出load(Person) initialize(Person) load(Student) initialize(Student)的顺序吧:
//第一个呢:在子类的Student.m的load方法中写法如下,Person的load方法不实现:
+(void)load
{
[Student class];
[Person class];
}
//第二个呢:在父类的Person.m的load方法中写法如下,Student的load方法不实现
+(void)load
{
[Student class];
[Person class];
}
顺序结果以及解释
- 第一个呢:load(Person) >> load(Student) >> initialize(Person) >> initalize(Student) (>>早于)
- 第一次调用Person以及Student类对象是在Student 的load方法里面,那么父类Person会首先收到执行load的消息,所以第一个执行。
- 还是a的前缀,第一次调用Person以及Student类对象是在Student 的load方法里面,所以Student的load方法是执行在他们所有的initialize方法之前的。
- initialize方法的执行顺序就是父类比子类先执行,所以最终顺序就是load(Person) >> load(Student) >> initialize(Person) >> initalize(Student)
- 第二个呢:load(Person) >> initalize(Person) >> initialize(Student) >> load(Student) (>>早于)
- 第一次调用Person以及Student类对象是在Person 的load方法里面,那么父类Person会首先执行load的消息,所以第一个执行。
- 在Person的load里面(这个时候Student的load方法是没有执行的,因为必须等到父类的load方法执行完毕之后才会执行子类的load方法,不信看上面的顺序描述呀)。
- 在Person的load方法里面优先调用了Student对象,但根据initalize方法的执行顺序,所以Person的initalize方法优先,其次是Student的initalize方法