作为 NSObject 类中的 2 个方法 initialize 和 load 一直被我们所熟知,但是又没有具体去深入的了解,今天结合 Apple 官方文档,我们来深入了解一下 initialize 和 load 方法 。
initialize
看文档可以得知 initialize 方法的作用是在 class 收到第一个消息之前初始化 class。
Initializes the class before it receives its first message.
+ (void)initialize;
- runtime 会在程序的 class 收到第一个消息之前给每一个 class 发送 initialize 消息,让 class 进行初始化。
- Superclasses 会在 Subclasses 之前收到 initialize 消息。
- initialize 方法是线程安全的,在 initialize 方法运行期间, class会被锁定,其他的线程无法向该 class 发送消息,所以我们尽量避免在 initialize 方法里面做复杂的实现。
接下来我们用代码来探究 initialize 这个方法,我们有 3 个 class ,分别是 Man,Woman,Person。 其中 Man 和 Woman 都继承自 Person 。
#import <Foundation/Foundation.h>
@interface Person : NSObject
@end
#import "Person.h"
@implementation Person
+(void)initialize{
NSLog(@"call person initialize");
}
@end
#import "Person.h"
@interface Man : Person
@end
#import "Man.h"
@implementation Man
@end
#import "Person.h"
@interface Woman : Person
@end
#import "Woman.h"
@implementation Woman
@end
在 main.m 方法代码如下:
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
}
return 0;
}
我们运行程序得到如下输出
call person initialize
在 Person 的 initialize 方法设置断点,查看堆栈调用,Person 调用的第一个方法确实是 initialize ,该方法在 Person 收到第一个消息之前初始化 Person。
image.png接下来修改 main.m 的代码,同时生成 Person ,Man,Woman 的对象实例。
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Man.h"
#import "Woman.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
Man *m = [[Man alloc] init];
Woman *w = [[Woman alloc] init];
}
return 0;
}
运行程序,查看控制台输出
call person initialize
call person initialize
call person initialize
我们可以看出如果子类没有实现 initialize 方法,runtime 会调用父类的 initialize 实现,那么我们就不用在子类中调用 [super initialize]
,同时也意味着父类的 initialize 会被多次调用,那么我们可以采用如下的方式来避免这个问题。
+ (void)initialize {
if (self == [ClassName self]) {
// ... do the initialization ...
}
}
那如果 Person子类 Man 和 Woman 都实现 initialize 方法呢?
@implementation Man
+(void)initialize{
NSLog(@"call man initialize");
}
+(void)initialize{
NSLog(@"call woman initialize");
}
@end
运行程序,查看控制台输出,可以看到 class 都是调用各自的 initialize 方法实现。
call person initialize
call man initialize
call woman initialize
每个 class 有且仅有一次 initialize 方法调用,如果想要实现 class 和 category 的分别独立初始化,我们应该使用 load 方法。
load
看文档可以得知 class 或者 category 被添加到 runtime 的时候,load 方法就会被调用。
Invoked whenever a class or category is added to the Objective-C runtime;
implement this method to perform class-specific behavior upon loading.
+ (void)load;
- 和 initialize 方法类似,class 的 load 方法会在 superlcasses 的 load 方法调用之后被调用。
- category 的 load 方法会在 class 的 load 方法调用之后被调用。
接下来我们用代码来探究 load 这个方法,还是用之前的例子,我们有 3 个 class ,分别是 Man,Woman,Person。 其中 Man 和 Woman 都继承自 Person 。
Person 类实现了 load 方法
//Person.m
#import "Person.h"
@implementation Person
+(void)initialize{
NSLog(@"call person initialize");
}
+(void)load{
NSLog(@"call person load");
}
// main.m
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Man.h"
#import "Woman.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
}
return 0;
}
@end
运行程序,查看控制台输出,可以看出 load 方法调用在 initialize 方法之前。
call person load
call person initialize
接下来修改 main.m 实现
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Man.h"
#import "Woman.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
Man *m = [[Man alloc] init];
Woman *w = [[Woman alloc] init];
}
return 0;
}
运行程序,查看控制台输出,可以看出子类没有实现 load 方法的时候,runtime 不会自动调用父类的 load 方法实现
call person load
call person initialize
call man initialize
call woman initialize
接下来修改 Man.m 和 Woman.m 实现
// Man.m
#import "Man.h"
@implementation Man
+(void)initialize{
NSLog(@"call man initialize");
}
+(void)load{
NSLog(@"call man load");
}
@end
// Woman.m
#import "Woman.h"
@implementation Woman
+(void)initialize{
NSLog(@"call woman initialize");
}
+(void)load{
NSLog(@"call woman load");
}
@end
运行程序,查看控制台输出,可以看出,我们没有在 Woman 和 Man 中显式调用父类的 load 方法,但是父类的 load 方法调用都在子类之前,这个和 initialize 方法是一样的,毕竟是要先有父类初始化,才会有子类初始化。
call person load
call woman load
call man load
call person initialize
call man initialize
call woman initialize
接下来新建一个 Man 的 category 叫做 Man(Work),
// Mam + Work.m
#import "Man+Work.h"
@implementation Man (Work)
+(void)load{
NSLog(@"call Man (Work) load");
}
@end
运行程序,查看控制台输出,可以看出 category 的 load 方法调用总是在 class 的 load 方法调用之后。
call man load
call Man (Work) load
call man initialize
总结
通过一些代码例子和官方文档,我们可以知道 initialize 方法的作用是在 class 收到第一个消息之前初始化 class。load 方法的调用时机是在 class 或者 category 被添加到 runtime 的时候。load 方法调用在 initialize 方法之前。
网友评论
```
// 代码
@Implementation Man
+(void)initialize{
NSLog(@"%@ call man initialize",[self class]);
}
+(void)load{
NSLog(@"%@ call man load",[self class]);
}
@EnD
```
作者得出的结论:
> 运行程序,查看控制台输出,可以看出 load 方法调用在 initialize 方法之后。
-------------------------------------------------------------------------
上述示例是有问题的,理论上`load`在`initialize`之前调用的,这里之所以打log时先输出`initialize`中的NSLog、后输出`load`中的NSLog,是因为`load`中的 `[self class]`的方法调用(会触发调用`initialize`)。
所以正确的执行顺序是:load -> [self class] -> 触发调用initialize -> 打印initialize中的日志 -> 打印load中的日志。
所以文章中的举例是有问题的([self class]调用造成的),可以断点调试一下。