Osheep

时光不回头,当下最重要。

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",[self class]);
}

@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;
}

我们运行程序得到如下输出

Person call person initialize

在 Person 的 initialize 方法设置断点,查看堆栈调用,Person 调用的第一个方法确实是 initialize ,该方法在 Person 收到第一个消息之前初始化 Person。

《NSObject 的 initialize 和 load 方法》

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;
}

运行程序,查看控制台输出

Person call person initialize
Man call person initialize
Woman 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",[self class]);
}

+(void)initialize{
    NSLog(@"%@ call woman initialize",[self class]);
}
@end


运行程序,查看控制台输出,可以看到 class 都是调用各自的 initialize 方法实现。

Person call person initialize
Man call man initialize
Woman 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",[self class]);
}

+(void)load{
    NSLog(@"%@ call person load",[self class]);
}

// 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 方法之后。

Person call person initialize
Person 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];
        Man *m = [[Man alloc] init];
        Woman *w = [[Woman alloc] init];
    }
    return 0;
}

运行程序,查看控制台输出,可以看出 子类没有实现 load 方法的时候,runtime 不会自动调用父类的 load 方法实现

Person call person initialize
Person call person load
Man call man initialize
Woman call woman initialize

接下来修改 Man.m 和 Woman.m 实现


// Man.m
#import "Man.h"

@implementation Man
+(void)initialize{
    NSLog(@"%@ call man initialize",[self class]);
}

+(void)load{
    NSLog(@"%@ call man load",[self class]);
}
@end

// Woman.m
#import "Woman.h"

@implementation Woman

+(void)initialize{
    NSLog(@"%@ call woman initialize",[self class]);
}

+(void)load{
    NSLog(@"%@ call woman load",[self class]);
}

@end

运行程序,查看控制台输出,可以看出,我们没有在 Woman 和 Man 中显式调用父类的 load 方法,但是父类的 load 方法调用都在子类之前,这个和 initialize 方法是一样的,毕竟是要先有父类初始化,才会有子类初始化。

Person call person initialize
 Person call person load
Woman call woman initialize
Woman call woman load
Man call man initialize
Man call man load

接下来新建一个 Man 的 category 叫做 Man(Work),

// Mam + Work.m
#import "Man+Work.h"
@implementation Man (Work)
+(void)load{
    NSLog(@"%@ call Man (Work) load",[self class]);
}
@end

运行程序,查看控制台输出,可以看出 category 的 load 方法调用总是在 class 的 load 方法调用之后。

Man call man initialize
Man call man load
Man call Man (Work) load

总结

通过一些代码例子和官方文档,我们可以知道 initialize 方法的作用是在 class 收到第一个消息之前初始化 class。load 方法的调用时机是在 class 或者 category 被添加到 runtime 的时候。

参考

  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/
点赞