NSObject中类方法load和initialize

+load和+initialize是NSObject的两个类方法,这两个类方法会在类被使用时主动调用,但是调用时机和调用顺序却截然不同。

先看看苹果的官方文档里面

Initialize

###Initializing a Class
class func initialize()
Initializes the class before it receives its first message.

he runtime sends initialize() to each class in a program just before the class, or any class that inherits from it, is sent its first message from within the program. Superclasses receive this message before their subclasses.
The runtime sends the initialize() message to classes in a thread-safe manner. That is, initialize() is run by the first thread to send a message to a class, and any other thread that tries to send a message to that class will block until initialize() completes.
The superclass implementation may be called multiple times if subclasses do not implement initialize()—the runtime will call the inherited implementation—or if subclasses explicitly call [super initialize]. If you want to protect yourself from being run multiple times, you can structure your implementation along these lines:

1
2
3
4
5
+ (void)initialize {
if (self == [ClassName self]) {
// ... do the initialization ...
}
}

Because initialize() is called in a blocking manner, it’s important to limit method implementations to the minimum amount of work necessary possible. Specifically, any code that takes locks that might be required by other classes in their initialize() methods is liable to lead to deadlocks. Therefore, you should not rely on initialize() for complex initialization, and should instead limit it to straightforward, class local initialization.

initialize() is invoked only once per class. If you want to perform independent initialization for the class and for categories of the class, you should implement load() methods.

  1. initialize方法是在该类接收到第一个消息之前调用.
  2. 父类的 initialize 方法先于子类的 initialize 方法调用.
  3. 如果子类中没有实现 initialize 方法,或者子类显示调用父类实现 [super initialize], 那么则会调用其父类的实现。也就是说,父类的 initialize 可能会被调用多次。
  4. 在应用程序生命周期里,runtime 只会向每个类发送一次 initialize 消息. 所以如果category中实现了initialize方法,那么原来类中的则不会被调用.

看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@interface SuperObject : NSObject
@end

@implementation SuperObject
+ (void)initialize {
NSLog(@"%s", __FUNCTION__);
}
@end

@interface SubObject : SuperObject
@end

@implementation SubObject
+ (void)initialize {
NSLog(@"%s", __FUNCTION__);
}
@end

int main(int argc, char * argv[]) {
@autoreleasepool {
[SubObject class];
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

输出:

1
2
+[SuperObject initialize]
+[SubObject initialize]

就算把main函数中的[SubObject class]改为:

1
2
[SubObject class];
[[SubObject alloc] init];

输出还是一样。

添加一个category:

1
2
3
4
5
6
7
8
@interface SubObject (category)
@end

@implementation SubObject (category)
+ (void)initialize {
NSLog(@"%s", __FUNCTION__);
}
@end

输出则为(category中方法覆盖了原类中的方法):

1
2
+[SuperObject initialize]
+[SubObject(category) initialize]

load

###class func load()
Invoked whenever a class or category is added to the Objective-C runtime; implement this method to perform class-specific behavior upon loading.

The load() message is sent to classes and categories that are both dynamically loaded and statically linked, but only if the newly loaded class or category implements a method that can respond.
The order of initialization is as follows:

1. All initializers in any framework you link to.
2. All +load methods in your image.
3. All C++ static initializers and C/C++ __attribute__(constructor) functions in your image.
4. All initializers in frameworks that link to you.

In addition:

* A class’s +load method is called after all of its superclasses’ +load methods.
* A category +load method is called after the class’s own +load method.

In a custom implementation of load() you can therefore safely message other unrelated classes from the same image, but any load() methods implemented by those classes may not have run yet.

  1. 在类或者category被添加到runtime的时候调用,该调用发生在main函数之前。
  2. 父类load方法先于子类调用,类本身load方法先于category中调用。

看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@interface SuperObject : NSObject
@end

@implementation SuperObject
+ (void)initialize {
NSLog(@"%@ , %s", [self class], __FUNCTION__);
}
+ (void)load {
NSLog(@"%@, %s", [self class], __FUNCTION__);
}
@end

@interface SubObject : SuperObject
@end

@implementation SubObject
+ (void)initialize {
NSLog(@"%@, %s", [self class], __FUNCTION__);
}
+ (void)load {
NSLog(@"%@, %s", [self class], __FUNCTION__);
}
@end

@interface SubObject (category)
@end

@implementation SubObject (category)
+ (void)initialize {
NSLog(@"%@, %s", [self class], __FUNCTION__);
}
+ (void)load {
NSLog(@"%@ %s", [self class], __FUNCTION__);
}
@end

int main(int argc, char * argv[]) {
@autoreleasepool {
SubObject *subObject = [[SubObject alloc] init];
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

输出结果(这里有点地方要解释一下,按照文档的说法,load应该咋initialize之前,这里实在之后,原是因为,load方法中调用了class方法,从而触发了initialize方法,所以出现了这种情况):

1
2
3
4
5
SuperObject , +[SuperObject initialize]
SuperObject, +[SuperObject load]
SubObject, +[SubObject(category) initialize]
SubObject, +[SubObject load]
SubObject +[SubObject(category) load]

再看一个例子,子类中不实现initialize方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@interface SuperObject : NSObject
@end

@implementation SuperObject
+ (void)initialize {
NSLog(@"%@ , %s", [self class], __FUNCTION__);
}

@end

@interface SubObject : SuperObject
@end

@implementation SubObject
+ (void)load {
NSLog(@"%@, %s", [self class], __FUNCTION__);
}
@end

int main(int argc, char * argv[]) {
@autoreleasepool {
SubObject *subObject = [[SubObject alloc] init];
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

输出结果如下(子类自动调了父类的initialize):

1
2
3
SuperObject , +[SuperObject initialize]
SubObject , +[SuperObject initialize]
SubObject, +[SubObject load]

不需要在loadinitialize方法中显式的去调用父类的方法。

Author: y500
Link: https://www.y500.me/2017/11/15/NSObject/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.