NSObject 的 initialize 和 load 方法

作为 NSObject 类中的 2 个方法 initialize 和 load 一直被我们所熟知,但是又没有具体去深入的了解,今天结合 Apple 官方文档,我们来深入了解一下 initialize 和 load 方法 。

initialize

看文档可以得知 initialize 方法的作用是在 class 收到第一个消息之前初始化 class。

Initializes the class before it receives its first message.

+ (void)initialize;
复制代码
  1. runtime 会在程序的 class 收到第一个消息之前给每一个 class 发送 initialize 消息,让 class 进行初始化。
  2. Superclasses 会在 Subclasses 之前收到 initialize 消息。
  3. 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;
复制代码
  1. 和 initialize 方法类似,class 的 load 方法会在 superlcasses 的 load 方法调用之后被调用。
  2. 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 方法之前。

参考

  1. https://developer.apple.com/documentation/objectivec/nsobject/1418639-initialize?preferredLanguage=occ
  2. https://developer.apple.com/documentation/objectivec/nsobject/1418815-load?language=objc
  3. http://zhangbuhuai.com/initialize-and-load-in-objective-c/