前言
在iOS开发load和initialize都接触过,对于他们的区别,大多数开发者可能不是特别清楚。所以下面就详解一下load与initialize
load类方法
load类方法特点:
- 当类被导入到项目的时候就会执行load函数,
- 在main函数开始执行之前的,这个类是否被用到无关
- 每个类的load函数只会自动调用一次
- load函数是系统自动加载的,不需要调用父类的load函数
load类方法调用的特点:
- 父类和子类都实现load函数时,父类的load方法执行顺序要优先于子类。
创建两个类Person,Student。 Student继承Person
Person : NSObject
Student : Person
在Person.m、Student.m文件中实现load方法
//Person
@implementation Person
+ (void)load {
NSLog(@"%s",__FUNCTION__);
}
@end
//Student
@implementation Student
+ (void)load {
NSLog(@"%s",__FUNCTION__);
}
@end
然后运行代码:
[Person load]
[Student load]
执行结果可以得出父类的load方法执行顺序要优先于子类
- 子类未实现load方法时,不会调用父类load方法
创建一个Teacher继承与Person,创建完成,不去实现load方法,运行项目:
[Person load]
[Student load]
运行结果说明:没有实现load方法,不去调用父类load方法
- 类中的load方法执行顺序要优先于类别(Category)
创建3个对象Person (Category)、Person (Category2)、Person (Category3),并且分别实现load方法
//Person+Category.m
+(void)load {
NSLog(@"%s",__FUNCTION__);
}
//Person+Category2.m
+(void)load {
NSLog(@"%s",__FUNCTION__);
}
//Person+Category3.m
+(void)load {
NSLog(@"%s",__FUNCTION__);
}
在运行项目执行结果如下:
+[Person load]
+[Student load]
+[Person(Category1) load]
+[Person(Category3) load]
+[Person(Category2) load]
执行结果表明,类中的load方法执行顺序要优先于类别(Category)
- 有多个类别(Category)都实现了load方法,这几个load方法都会执行,但执行顺序不确定(其执行顺序与类别在Compile Sources中出现的顺序一致)
查看一下Compile Sources的文件顺序,如下图:
执行顺序和Compile Sources顺序是一样的。
- 有多个不同的类的时候,每个类load 执行顺序与其在Compile Sources出现的顺序一致
前面Teacher类 实现也load方法,如下:
//Teacher.h
+(void)load {
NSLog(@"%s",__FUNCTION__);
}
运行一下项目,执行结果:
[Person load]
[Teacher load]
[Student load]
[NSObject(Category2) load]
[NSObject(Category) load]
[NSObject(Category3) load]
在Compile Sources出现的顺序,Teacher在Student以前,Teacher与Student也不是继承关系,不同类执行顺序和Compile Sources一至。
initialize
initialize类方法的特点:
即使类文件被引用进项目,但是没有使用,initialize不会被调用
假如这个类放到代码中,而这段代码并没有被执行,这个函数是不会被执行的。
类或者其子类的第一个方法被调用前调用
由于是系统自动调用,也不需要再调用 [super initialize] ,否则父类的initialize会被多次执行
initialize类方法调用的特点:
父类的initialize方法会比子类先执行
子类未实现initialize方法时,会调用父类initialize方法,子类实现initialize方法时,会覆盖父类initialize方法.
当有多个Category都实现了initialize方法,会覆盖类中的方法,只执行一个(会执行Compile Sources 列表中最后一个Category 的initialize方法)
下面我通过代码来打印验证:
//Person.m
+(void)initialize {
NSLog(@"%s",__FUNCTION__);
}
执行结果:
没有打印
- (void)viewDidLoad {
[super viewDidLoad];
return;
//这块代码不会执行,也不会执行initialize
[Person new];
}
执行结果:
没有打印
表明只要不执行类的第一方法,是不会执行的。
//执行这个代码
[Person new];
执行结果:
[Person initialize]
我们在Student.m(继承Person)中不实现initialize方法,
//Person.m
+(void)initialize {
NSLog(@"%s",__FUNCTION__);
}
//Student.m
Student中不实现initialize方法
执行一下
[Student new];
直接结果:
[Person initialize]
[Person initialize]
为啥执行两遍,因为父类会比子类先执行,先执行父类的,子类没有实现的,会调用父类initialize方法,所以执行了两遍。
如果把上面的代码改成:
//在 Person.h
+(void)initialize
{
if(self == [Person class])
{
NSLog(@"%s",__FUNCTION__);
}
}
执行结果,只执行一遍。
那我们在Student也实现initialize方法:
//Person.m
+(void)initialize {
NSLog(@"%s",__FUNCTION__);
}
//Student.m
+(void)initialize {
NSLog(@"%s",__FUNCTION__);
}
执行一下
[Student new];
可以得出当子类实现initialize方法后,会覆盖父类initialize方法.
当Person的类别也实现initialize方法,如下:
//Person.m
+(void)initialize {
NSLog(@"%s",__FUNCTION__);
}
//Person+Category.m
+(void)initialize {
NSLog(@"%s",__FUNCTION__);
}
执行结果是:
[Person(Category) initialize]
运行后可以看到Person的initialize方法并没有被执行,已经被Person+Category中的initialize方法覆盖了。
如果是多个Person的Category都实现了initialize,执行结果会如何,如下:
//Person.m
+(void)initialize {
NSLog(@"%s",__FUNCTION__);
}
//Person+Category.m
+(void)initialize {
NSLog(@"%s",__FUNCTION__);
}
//Person+Category2.m
+(void)initialize {
NSLog(@"%s",__FUNCTION__);
}
//Person+Category3.m
+(void)initialize {
NSLog(@"%s",__FUNCTION__);
}
运行:
[Person new];
直接结果如下:
[Person(Category2) initialize]
initialize方法对一个类而言只会调用一次,类中initialize比Category的initialize优先级低,多个Category执行Compile Sources最后一个Category的initialize方法
看compile source 文件中顺序Person+Category2.m个category最后执行的。
Load使用情况
load很常见的一个使用场景,交换两个方法的实现,称之为method swizzling
+(void)load {
static dispatch_once_t oneToken;
dispatch_once(&oneToken, ^{
Method imageNamed = class_getClassMethod(self,@selector(imageNamed:));
Method mkeImageNamed =class_getClassMethod(self,@selector(swizze_imageNamed:));
method_exchangeImplementations(imageNamed, mkeImageNamed);
});
}
+ (instancetype)swizze_imageNamed:(NSString*)name {
//这个时候swizze_imageNamed已经和imageNamed交换imp,所以当你在调用swizze_imageNamed时候其实就是调用imageNamed
UIImage * image;
if( IS_IPHONE ){
// iphone处理
UIImage * image = [self swizze_imageNamed:name];
if (image != nil) {
return image;
} else {
return nil;
}
} else {
// ipad处理,_ipad是自己定义,~ipad是系统自己自定义。
UIImage *image = [self swizze_imageNamed:[NSString stringWithFormat:@"%@_ipad",name]];
if (image != nil) {
return image;
}else {
image = [self swizze_imageNamed:name];
return image;
}
}
这个就是抵用系统imageNamed方法,在这基础上对ipad的图片进行适配,这样的每次调用imageNamed
不用在对ipad和iphone进行适配。
initialize使用情况
initialize方法主要用来对一些不方便在编译期初始化的对象进行赋值。
比如NSMutableArray这种类型的实例化依赖于runtime的消息发送,所以显然无法在编译器初始化:
// In Person.m
// int类型可以在编译期赋值
static int someNumber = 0;
static NSMutableArray *someArray;
+ (void)initialize {
if (self == [Person class]) {
// 不方便编译期复制的对象在这里赋值
someArray = [[NSMutableArray alloc] init];
}
}
总结
load和initialize方法都会在实例化对象之前调用
load执行在main函数以前,initialize执行main函数之后
这两个方法会被自动调用,不能手动调用它们。
load和initialize方法都不用显示的调用父类的方法而是自动调用
子类没有initialize方法也会调用父类的方法,而load方法则不会调用父类
initialize方法对一个类而言只会调用一次(Person、或者是Person+Category)都是一个Perosn类。load方法则是每个都会调用,只要你写了load方法,添加到工程都会实现。
load方法通常用来进行Method Swizzle,initialize方法一般用于初始化全局变量或静态变量。
load和initialize方法内部使用了锁,因此它们是线程安全的。实现时要尽可能保持简单,避免阻塞线程,不要再使用锁。
结尾
load和initialize这块特别容易混淆了,所以大家多多实践,在实践中慢慢理解。大家加油!!!