NSObject 的 initialize 和 load 方法

要上班的斌哥發表於2019-03-04

作為 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/

相關文章