iOS-Objective C類方法load和initialize的區別

Objective C類方法load和initialize的區別過去兩個星期里,為了完成一個工作,接觸到了NSObject中非常特別的兩個類方法(Class Method)。它們的特別之處,在于iOS會在運行期提前并且自動調用這兩個方法,而且很多對于類方法的規則(比如繼承,類別(Category))都有不同的處理。
而因為這兩個方法是在程序運行一開始就被調用的方法,我們可以利用他們在類被使用前,做一些預處理工作。比如我碰到的就是讓類自動將自身類名保存到一個NSDictionary中。

先來看看NSObject Class Reference里對這兩個方法說明:
+(void)initializeThe runtime sends initialize to each class in a program exactly one time just before the class, or any class that inherits from it, is sent its first message from within the program. (Thus the method may never be invoked if the class is not used.) The runtime sends the initialize message to classes in a thread-safe manner. Superclasses receive this message before their subclasses.
+(void)loadThe 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.
Apple的文檔很清楚地說明了initialize和load的區別在于:load是只要類所在文件被引用就會被調用,而initialize是在類或者其子類的第一個方法被調用前調用。所以如果類沒有被引用進項目,就不會有load調用;但即使類文件被引用進來,但是沒有使用,那么initialize也不會被調用。
它們的相同點在于:方法只會被調用一次。(其實這是相對runtime來說的,后邊會做進一步解釋)。
文檔也明確闡述了方法調用的順序:父類(Superclass)的方法優先于子類(Subclass)的方法,類中的方法優先于類別(Category)中的方法。
不過還有很多地方是文章中沒有解釋詳細的。所以再來看一些示例代碼來明確其中應該注意的細節。
+(void)load與+(void)initialize初探
1 +(void)load會引發+(void)initialize
2 /******* Interface *******/
3 @interface SuperClass : NSObject
4 @end
5
6 @interface ChildClass : SuperClass
7 @end
8
9 @interface Insideinitialize : NSObject
10 - (void)objectMethod;
11 @end
12

13 /******* Implementation *******/
14 @implementation SuperClass
15
16 + (void) initialize {
17 NSLog(@"%@ %s", [self class], FUNCTION);
18 }
19
20 + (void) load {
21 NSLog(@"%@ %s", [self class], FUNCTION);
22 }
23
24 @end
25
26 @implementation ChildClass
27
28 + (void) initialize {
29 NSLog(@"%@ %s", [self class], FUNCTION);
30 Insideinitialize * obj = [[Insideinitialize alloc] init];
31 [obj objectMethod];
32 [obj release];
33 }
34
35 @end
36
37 @implementation Insideinitialize
38
39 - (void)objectMethod {
40 NSLog(@"%@ %s", [self class], FUNCTION);
41 }
42
43 + (void) initialize {
44 NSLog(@"%@ %s", [self class], FUNCTION);
45 }
46
47 + (void) load {
48 NSLog(@"%s", FUNCTION);
49 }
50
51 @end

這個示例代碼中,一個SuperClass實現了+(void)load和+(void)initialize方法(實際上應該算是重寫覆蓋了NSObject的這兩個方法);ChildClass繼承于SuperClass,但是只重寫+(void)initialize沒有+(void)load;Insideinitialize類也有+(void)load和+(void)initialize方法,它在ChildClass的i+(void)initialize方法中被構建出一個對象。類中的每個函數的實現都非常簡單,只是輸出類名和方法名。除了Insideinitialize的+(void)load方法只輸出了類名,沒有使用[self class]。
首先我們在Xcode的項目中只簡單import這些類,而不去使用他們的,然后運行項目就會得到下邊的結果:
SuperClass +[SuperClass initialize]
SuperClass +[SuperClass load]
Insideinitialize +[Insideinitialize load]

就像Apple的文檔中說的一下,只要有引用runtime就會自動去調用類的+(void)load方法。不過從輸出中,我們還發現SuperClass的+(void)initialize也被調用了,而且是在+(void)load之前被執行;而Insideinitialize的+(void)initialize并沒有執行。這是因為在SuperClass的+(void)load方法中,我們調用了類的class方法([self class]),這就符合文檔中對+(void)initialize的說明:在類的第一個方法被調用前調用。同時也說明runtime對+(void)load的調用并不視為類的第一個方法。而ChildClass因為沒有用到,所以+(void)initialize的方法被沒有被執行,而且它也沒有去執行父類的+(void)load方法(雖然它有繼承下該方法)。
+(void)load和+(void)initialize可當做普通類方法(Class Method)被調用接著, 在程序中讓ChildClass直接調用load:
[ChildClass load];

程序正常運行,并輸出了結果:
SuperClass +[SuperClass initialize]
SuperClass +[SuperClass load]
+[Insideinitialize load]
ChildClass +[ChildClass initialize]
Insideinitialize +[Insideinitialize initialize]
Insideinitialize -[Insideinitialize objectMethod]
ChildClass +[SuperClass load]

前面三個結果跟之前一樣,不過之后ChildClass的+(void)initialize也被自動執行調用,并且我們可以在其中安全創建出Insideinitialize類并使用它,而Insideinitialize因為調用alloc方法是第一次使用類方法, 所以激發了Insideinitialize的+(void)initialize。
另一個方面,ChildClass繼承下了+(void)load而且可以被安全地當做普通類方法(Class Method)被使用。這也就是我之前所說的load和initialize被調用一次是相對runtime而言(比如SuperClass的initialize不會因為自身load方法調用一次,又因為子類調用了load又執行一次),我們依然可以直接去反復調用這些方法。
子類會調用父類的+(void)initialize接下來,我們再修改一下SuperClass和ChildClass:去掉SuperClass中的+(void)load方法;讓ChildClass來重寫+(void)load,但是去掉+(void)initialize。

1 /******* Interface *******/
2 @interface SuperClass : NSObject
3 @end
4
5 @interface ChildClass : SuperClass
6 @end
7
8 @interface Insideinitialize : NSObject
9 - (void)objectMethod;
10 @end
11

12 /******* Implementation *******/
13 @implementation SuperClass
14
15 + (void) initialize {
16 NSLog(@"%@ %s", [self class], FUNCTION);
17 }
18
19 @end
20
21 @implementation ChildClass
22
23 + (void) load {
24 NSLog(@"%@ %s", [self class], FUNCTION);
25 }
26
27 @end

依然還是簡單的引入這些類,并不去使用它們。運行之后,我們會得到這樣的結果:
SuperClass +[SuperClass initialize]
ChildClass +[SuperClass initialize]
ChildClass +[ChildClass load]

和之前一樣,+(void)load會引起+(void)initialize。也很Apple文檔中講得那樣,子類方法的調用會激起父類的+(void)initialize被執行。不過我們也看到雖然ChildClass沒有定義+(void)initialize,但是它會使用父類的+(void)initialize。而之前的示例,我們看到子類并不會在runtime時去使用父類的+(void)load,也就是說只有新定義的+(void)load才會被runtime去調用執行。
類別(Category)中的+(void)load的+(void)initialize我們再來看看類實現(@implementation)和類的類別(Category)中+(void)load和+(void)initialize的區別。

1 /******* Interface *******/
2 @interface MainClass : NSObject
3 @end
4
5 /******* Category Implementation *******/
6 @implementation MainClass(Category)
7
8 + (void) load {
9 NSLog(@"%@ %s", [self class], FUNCTION);
10 }
11
12 + (void) initialize {
13 NSLog(@"%@ %s", [self class], FUNCTION);
14 }
15
16 @end
17
18 @implementation MainClass(OtherCategory)
19
20 + (void) load {
21 NSLog(@"%@ %s", [self class], FUNCTION);
22 }
23
24 + (void) initialize {
25 NSLog(@"%@ %s", [self class], FUNCTION);
26 }
27
28 @end
29
30 /******* Implementation *******/
31 @implementation MainClass
32
33 + (void) load {
34 NSLog(@"%@ %s", [self class], FUNCTION);
35 }
36
37 + (void) initialize {
38 NSLog(@"%@ %s", [self class], FUNCTION);
39 }
40
41 @end

簡單import,運行,我們看到的結果是:
MainClass +[MainClass(OtherCategory) initialize]
MainClass +[MainClass load]
MainClass +[MainClass(Category) load]
MainClass +[MainClass(OtherCategory) load]

同樣的+(void)initialize優先于+(void)load先執行。但是很明顯的不同在于,只有最后一個類別(Category)的+(void)initialize執行,其他兩個都被隱藏。而對于+(void)load,三個都執行,并且如果Apple的文檔中介紹順序一樣:先執行類自身的實現,再執行類別(Category)中的實現。
Runtime調用+(void)load時沒有autorelease pool最后再來看一個示例

1 @interface MainClass : NSObject
2 @end
3
4 @implementation MainClass
5
6 + (void) load {
7 NSArray *array = [NSArray array];
8 NSLog(@"%@ %s", array, FUNCTION);
9 }
10
11 @end

運行這段代碼,Xcode給出如下的信息:
objc[84934]: Object 0x10a512930 of class __NSArrayI autoreleased with no pool in place - just leaking - break on objc_autoreleaseNoPool() to debug
2012-09-28 18:07:39.042 ClassMethod[84934:403] (
) +[MainClass load]

其原因是runtime調用+(void)load的時候,程序還沒有建立其autorelease pool,所以那些會需要使用到autorelease pool的代碼,都會出現異常。這一點是非常需要注意的,也就是說放在+(void)load中的對象都應該是alloc出來并且不能使用autorelease來釋放。
不需要顯示使用super調用父類中的方法當我們定義-(id)init和-(void)dealloc方法時,我們總是需要使用super關鍵字來調用父類的方法,讓父類也完成相同的操作。這是因為對對象的初始化和銷毀過程,Objective-C不像C++,C#那樣會自動調用父類默認構造函數。因此我們總是需要將這兩個函數寫成這樣:

1 - (id)init {
2 if ((self = [super init])) {
3 //do initialization
4 }
5
6 return self;
7 }
8
9 - (void)dealloc {
10 //do release
11
12 [super dealloc];13 }

但是+(void)initialize和+(void)load不同,我們并不需要在這兩個方法的實現中使用super調用父類的方法:

1 + (void)initialize {
2 //do initialization thing
3 [super initialize];
4 }
5
6 + (void) load {
7 //do some loading things
8 [super load];
9 }

super的方法會成功調用,但是這是多余的,因為runtime對自動對父類的+(void)load方法進行調用,而+(void)initialize則會隨子類自動激發父類的方法(如Apple文檔中所言)不需要顯示調用。另一方面,如果父類中的方法用到的self(像示例中的方法),其指代的依然是類自身,而不是父類。
總結:
+(void)load+(void)initialize執行時機在程序運行后立即執行在類的方法第一次被調時執行若自身未定義,是否沿用父類的方法?否是類別中的定義全都執行,但后于類中的方法覆蓋類中的方法,只執行一個
References:

1. Objective-C Class Loading and Initialization

2. Cocoa Application Startup

3. +initialize Can Be Executed Multiple Times (+load not so much)

4. NSObject Class Reference

5. Should +initialize/+load always start with an: if (self == [MyClass class]) guard? – stackoverflow

6. [super initialize] – Apple Mailing Lists

7. 

Objective-C類初始化:load與initialize

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,156評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,401評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,069評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,873評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,635評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,128評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,203評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,365評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,881評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,733評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,935評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,475評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,172評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,582評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,821評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,595評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,908評論 2 372

推薦閱讀更多精彩內容