iOS類方法load和initialize詳解

iOS開發中總能看到+load和+initialize的身影,網上對于這兩個方法有很多解釋,官方也有說明,但有些細節不夠清楚,今天我們來詳細扒一扒這兩個方法.

load

Apple文檔是這樣描述的

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

當類(Class)或者類別(Category)加入Runtime中時(就是被引用的時候)。
實現該方法,可以在加載時做一些類特有的操作。

Discussion

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:

All initializers in any framework you link to.
調用所有的Framework中的初始化方法

All +load methods in your image.
調用所有的+load方法

All C++ static initializers and C/C++ attribute(constructor) functions in your image.
調用C++的靜態初始化方及C/C++中的attribute(constructor)函數

All initializers in frameworks that link to you.
調用所有鏈接到目標文件的framework中的初始化方法

In addition:

A class’s +load method is called after all of its superclasses’ +load methods.
一個類的+load方法在其父類的+load方法后調用

A category +load method is called after the class’s own +load method.
一個Category的+load方法在被其擴展的類的自有+load方法后調用

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.
在+load方法中,可以安全地向同一二進制包中的其它無關的類發送消息,但接收消息的類中的+load方法可能尚未被調用。

文檔地址:https://developer.apple.com/reference/objectivec/nsobject/1418815-load?language=objc

load函數調用特點如下:

當類被引用進項目的時候就會執行load函數(在main函數開始執行之前),與這個類是否被用到無關,每個類的load函數只會自動調用一次.由于load函數是系統自動加載的,因此不需要調用父類的load函數,否則父類的load函數會多次執行。

  • 1.當父類和子類都實現load函數時,父類的load方法執行順序要優先于子類
  • 2.當子類未實現load方法時,不會調用父類load方法
  • 3.類中的load方法執行順序要優先于類別(Category)
  • 4.當有多個類別(Category)都實現了load方法,這幾個load方法都會執行,但執行順序不確定(其執行順序與類別在Compile Sources中出現的順序一致)
  • 5.當然當有多個不同的類的時候,每個類load 執行順序與其在Compile Sources出現的順序一致

下面通過實例來一起驗證下:

我們新建2個類:Person繼承NSObject,Student和Teacher均繼承Person


Person : NSObject
Student : Person
Teacher : Person

新建3個Person分類:

Person (Category)
Person (Category2)
Person (Category3)

在Person,Student,Person (Category),Person (Category2),Person (Category3)的.m文件中實現下面方法(Teacher除外)

//In Person.m
+(void)load
{
    NSLog(@"%s",__FUNCTION__);
}
//In Student.m(繼承自Person)
+(void)load
{
    NSLog(@"%s",__FUNCTION__);
}

//In Teacher.m(繼承Person)
不實現load方法

//In Person+Category.m
+(void)load
{
    NSLog(@"%s",__FUNCTION__);
}
//In Person+Category2.m
+(void)load
{
    NSLog(@"%s",__FUNCTION__);
}
//In Person+Category3.m
+(void)load
{
    NSLog(@"%s",__FUNCTION__);
}

運行結果:


2017-05-04 11:11:40.612 LoadAndInitializeExample[27745:856244] +[Person load]
2017-05-04 11:11:40.615 LoadAndInitializeExample[27745:856244] +[Student load]
2017-05-04 11:11:40.616 LoadAndInitializeExample[27745:856244] +[Person(Category3) load]
2017-05-04 11:11:40.617 LoadAndInitializeExample[27745:856244] +[Person(Category) load]
2017-05-04 11:11:40.617 LoadAndInitializeExample[27745:856244] +[Person(Category2) load]

可以看到執行順序依次為:

  • 1.首先執行的是父類Person load方法,再執行的是子類Student load方法,說明父類的load方法執行順序要優先于子類
  • 2.子類Teacher中沒有實現load方法,沒有打印,說明子類沒有實現load方法時并不會調用父類load方法
  • 3.最后執行的是Person 3個Category的load方法,并且沒有順序,說明類別(Category)中的load方法要晚于類,多個類別(Category)都實現了load方法,這幾個load方法都會執行,但執行順序不確定
  • 4.同時我們也可以看到這幾個Category load 執行順序與其在Compile Sources中出現的順序一致
  • 5.當然多個不同的類 其load執行順序,也與其在Compile Sources出現的順序一致
Compile Sources.png

initialize:

Apple文檔是這樣描述的

Initializes the class before it receives its first message.

在這個類接收第一條消息之前調用。

Discussion

The 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.

Runtime在一個程序中每一個類的一個程序中發送一個初始化一次,或是從它繼承的任何類中,都是在程序中發送第一條消息。(因此,當該類不使用時,該方法可能永遠不會被調用。)運行時發送一個線程安全的方式初始化消息。父類的調用一定在子類之前。

文檔地址:https://developer.apple.com/reference/objectivec/nsobject/1418639-initialize?language=objc

initialize函數調用特點如下:

initialize在類或者其子類的第一個方法被調用前調用。即使類文件被引用進項目,但是沒有使用,initialize不會被調用。由于是系統自動調用,也不需要再調用 [super initialize] ,否則父類的initialize會被多次執行。假如這個類放到代碼中,而這段代碼并沒有被執行,這個函數是不會被執行的。

  • 1.父類的initialize方法會比子類先執行
  • 2.當子類未實現initialize方法時,會調用父類initialize方法,子類實現initialize方法時,會覆蓋父類initialize方法.
  • 3.當有多個Category都實現了initialize方法,會覆蓋類中的方法,只執行一個(會執行Compile Sources 列表中最后一個Category 的initialize方法)

我們同樣通過實例來一起驗證下:

我們添加Person類,在.m中實現+ initialize方法

//In Person.m
+(void)initialize
{
     NSLog(@"%s",__FUNCTION__);
}

運行結果:

無打印...

啥也沒打印

說明:只是把類文件被引用進項目,沒有使用的話,initialize不會被調用

我們在Teacher(繼承Person).m中不實現initialize方法


//In Person.m
+(void)initialize
{
     NSLog(@"%s",__FUNCTION__);
}

//In Teacher.m(繼承自Person)

Teacher.m中不實現initialize方法

我們調用下Teacher的new方法,運行


[Teacher new];

運行結果:

2017-05-04 14:06:45.051 LoadAndInitializeExample[29238:912579] +[Person initialize]
2017-05-04 14:06:45.051 LoadAndInitializeExample[29238:912579] +[Person initialize]

運行后發現父類的initialize方法竟然調用了兩次:

可見當子類未實現initialize方法,會調用父類initialize方法.

但為什么會打印2次呢?

我的理解:
子類不實現initialize方法,會把繼承父類的initialize方法并調用一遍。在此之前,父類初始化時,會先調用一遍自己initialize方法.所以出現2遍,所以為了防止父類initialize中代碼多次執行,我們應該這樣寫:

//In Person.m
+(void)initialize
{
    if(self == [Person class])
    {
          NSLog(@"%s",__FUNCTION__);
    }
}

下面我們在 Teacher.m中實現initialize方法

//In Person.m
+(void)initialize
{
     NSLog(@"%s",__FUNCTION__);
}

//In Teacher.m(繼承自Person)
+(void)initialize
{
     NSLog(@"%s",__FUNCTION__);
}

同樣調用Teacher 的new方法,運行


[Teacher new];

運行結果:

2017-05-04 14:07:45.051 LoadAndInitializeExample[29238:912579] +[Person initialize]
2017-05-04 14:07:45.051 LoadAndInitializeExample[29238:912579] +[Teacher initialize]

可以看到 當子類實現initialize方法后,會覆蓋父類initialize方法,這一點和繼承思想一樣

我們在Person.m和Person+Category.m中實現initialize方法

//In Person.m
+(void)initialize
{
     NSLog(@"%s",__FUNCTION__);
}

//In Person+Category.m
+(void)initialize
{
     NSLog(@"%s",__FUNCTION__);
}

調用Person 的 new方法,運行

[Person new];

運行結果:

2017-05-05 09:46:51.054 LoadAndInitializeExample[38773:1226306] +[Person(Category) initialize]

運行后可以看到Person的initialize方法并沒有被執行,已經被Person+Category中的initialize取代了

當有多個Category時會怎樣了,我們在Person,Person+Category,Person+Category2,Person+Category3 的.m中都實現initialize方法

//In Person.m
+(void)initialize
{
     NSLog(@"%s",__FUNCTION__);
}

//In Person+Category.m
+(void)initialize
{
     NSLog(@"%s",__FUNCTION__);
}
//In Person+Category2.m
+(void)initialize
{
     NSLog(@"%s",__FUNCTION__);
}
//In Person+Category3.m
+(void)initialize
{
     NSLog(@"%s",__FUNCTION__);
}

調用Person new方法,運行

[Person new];

運行結果:

2017-05-05 09:49:38.853 LoadAndInitializeExample[38825:1227819] +[Person(Category2) initialize]

可以看到,當存在多個Category時,也只執行一個,具體執行哪一個Category中的initialize方法,測試后便可發現,會執行Compile Sources 列表中最后一個Category 的initialize方法

Compile Sources.png

什么情況下使用:

+load

由于調用load方法時的環境很不安全,我們應該盡量減少load方法的邏輯。另一個原因是load方法是線程安全的,它內部使用了鎖,所以我們應該避免線程阻塞在load方法中

load很常見的一個使用場景,交換兩個方法的實現

//摘自MJRefresh
+ (void)load
{
    [self exchangeInstanceMethod1:@selector(reloadData) method2:@selector(mj_reloadData)];
    [self exchangeInstanceMethod1:@selector(reloadRowsAtIndexPaths:withRowAnimation:) method2:@selector(mj_reloadRowsAtIndexPaths:withRowAnimation:)];
    [self exchangeInstanceMethod1:@selector(deleteRowsAtIndexPaths:withRowAnimation:) method2:@selector(mj_deleteRowsAtIndexPaths:withRowAnimation:)];
    [self exchangeInstanceMethod1:@selector(insertRowsAtIndexPaths:withRowAnimation:) method2:@selector(mj_insertRowsAtIndexPaths:withRowAnimation:)];
    [self exchangeInstanceMethod1:@selector(reloadSections:withRowAnimation:) method2:@selector(mj_reloadSections:withRowAnimation:)];
    [self exchangeInstanceMethod1:@selector(deleteSections:withRowAnimation:) method2:@selector(mj_deleteSections:withRowAnimation:)];
    [self exchangeInstanceMethod1:@selector(insertSections:withRowAnimation:) method2:@selector(mj_insertSections:withRowAnimation:)];
}

+ (void)exchangeInstanceMethod1:(SEL)method1 method2:(SEL)method2
{
    method_exchangeImplementations(class_getInstanceMethod(self, method1), class_getInstanceMethod(self, method2));
}

+initialize

initialize方法主要用來對一些不方便在編譯期初始化的對象進行賦值。比如NSMutableArray這種類型的實例化依賴于runtime的消息發送,所以顯然無法在編譯器初始化:


// In Person.m
// int類型可以在編譯期賦值
static int someNumber = 0; 
static NSMutableArray *someArray;
+ (void)initialize {
    if (self == [Person class]) {
        // 不方便編譯期復制的對象在這里賦值
        someArray = [[NSMutableArray alloc] init];
    }
}

總結:

load和initialize的共同點

1.如果父類和子類都被調用,父類的調用一定在子類之前

+load方法要點

當類被引用進項目的時候就會執行load函數(在main函數開始執行之前),與這個類是否被用到無關,每個類的load函數只會自動調用一次.由于load函數是系統自動加載的,因此不需要再調用[super load],否則父類的load函數會多次執行。

  • 1.當父類和子類都實現load函數時,父類的load方法執行順序要優先于子類
  • 2.當一個類未實現load方法時,不會調用父類load方法
  • 3.類中的load方法執行順序要優先于類別(Category)
  • 4.當有多個類別(Category)都實現了load方法,這幾個load方法都會執行,但執行順序不確定(其執行順序與類別在Compile Sources中出現的順序一致)
  • 5.當然當有多個不同的類的時候,每個類load 執行順序與其在Compile Sources出現的順序一致

注意:
load調用時機比較早,當load調用時,其他類可能還沒加載完成,運行環境不安全.
load方法是線程安全的,它使用了鎖,我們應該避免線程阻塞在load方法.


+initialize方法要點

initialize在類或者其子類的第一個方法被調用前調用。即使類文件被引用進項目,但是沒有使用,initialize不會被調用。由于是系統自動調用,也不需要顯式的調用父類的initialize,否則父類的initialize會被多次執行。假如這個類放到代碼中,而這段代碼并沒有被執行,這個函數是不會被執行的。

  • 1.父類的initialize方法會比子類先執行
  • 2.當子類不實現initialize方法,會把父類的實現繼承過來調用一遍。在此之前,父類的方法會被優先調用一次
  • 3.當有多個Category都實現了initialize方法,會覆蓋類中的方法,只執行一個(會執行Compile Sources 列表中最后一個Category 的initialize方法)

注意:
在initialize方法收到調用時,運行環境基本健全。
initialize內部也使用了鎖,所以是線程安全的。但同時要避免阻塞線程,不要再使用鎖


以上總結可能并不完全,歡迎留言補充.....
代碼地址:https://github.com/CoderZhuXH/MyBlogExample

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

推薦閱讀更多精彩內容