iOS類方法load和initialize詳解

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

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.

當(dāng)類(Class)或者類別(Category)加入Runtime中時(就是被引用的時候)吗跋。
實(shí)現(xiàn)該方法火窒,可以在加載時做一些類特有的操作航闺。

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.
調(diào)用所有的Framework中的初始化方法

All +load methods in your image.
調(diào)用所有的+load方法

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

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

In addition:

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

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

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方法中,可以安全地向同一二進(jìn)制包中的其它無關(guān)的類發(fā)送消息,但接收消息的類中的+load方法可能尚未被調(diào)用借尿。

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

load函數(shù)調(diào)用特點(diǎn)如下:

當(dāng)類被引用進(jìn)項(xiàng)目的時候就會執(zhí)行l(wèi)oad函數(shù)(在main函數(shù)開始執(zhí)行之前),與這個類是否被用到無關(guān),每個類的load函數(shù)只會自動調(diào)用一次.由于load函數(shù)是系統(tǒng)自動加載的民鼓,因此不需要調(diào)用父類的load函數(shù),否則父類的load函數(shù)會多次執(zhí)行霹琼。

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

下面通過實(shí)例來一起驗(yàn)證下:

我們新建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文件中實(shí)現(xiàn)下面方法(Teacher除外)

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

//In Teacher.m(繼承Person)
不實(shí)現(xiàn)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__);
}

運(yùn)行結(jié)果:


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]

可以看到執(zhí)行順序依次為:

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

initialize:

Apple文檔是這樣描述的

Initializes the class before it receives its first message.

在這個類接收第一條消息之前調(diào)用务傲。

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在一個程序中每一個類的一個程序中發(fā)送一個初始化一次凉当,或是從它繼承的任何類中,都是在程序中發(fā)送第一條消息售葡。(因此看杭,當(dāng)該類不使用時,該方法可能永遠(yuǎn)不會被調(diào)用挟伙。)運(yùn)行時發(fā)送一個線程安全的方式初始化消息楼雹。父類的調(diào)用一定在子類之前。

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

initialize函數(shù)調(diào)用特點(diǎn)如下:

initialize在類或者其子類的第一個方法被調(diào)用前調(diào)用尖阔。即使類文件被引用進(jìn)項(xiàng)目,但是沒有使用,initialize不會被調(diào)用贮缅。由于是系統(tǒng)自動調(diào)用,也不需要再調(diào)用 [super initialize] 介却,否則父類的initialize會被多次執(zhí)行谴供。假如這個類放到代碼中,而這段代碼并沒有被執(zhí)行齿坷,這個函數(shù)是不會被執(zhí)行的憔鬼。

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

我們同樣通過實(shí)例來一起驗(yàn)證下:

我們添加Person類,在.m中實(shí)現(xiàn)+ initialize方法

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

運(yùn)行結(jié)果:

無打印...

啥也沒打印

說明:只是把類文件被引用進(jìn)項(xiàng)目,沒有使用的話,initialize不會被調(diào)用

我們在Teacher(繼承Person).m中不實(shí)現(xiàn)initialize方法


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

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

Teacher.m中不實(shí)現(xiàn)initialize方法

我們調(diào)用下Teacher的new方法,運(yùn)行


[Teacher new];

運(yùn)行結(jié)果:

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

運(yùn)行后發(fā)現(xiàn)父類的initialize方法竟然調(diào)用了兩次:

可見當(dāng)子類未實(shí)現(xiàn)initialize方法,會調(diào)用父類initialize方法.

但為什么會打印2次呢?

我的理解:
子類不實(shí)現(xiàn)initialize方法,會把繼承父類的initialize方法并調(diào)用一遍胃夏。在此之前,父類初始化時,會先調(diào)用一遍自己initialize方法.所以出現(xiàn)2遍,所以為了防止父類initialize中代碼多次執(zhí)行,我們應(yīng)該這樣寫:

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

下面我們在 Teacher.m中實(shí)現(xiàn)initialize方法

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

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

同樣調(diào)用Teacher 的new方法,運(yùn)行


[Teacher new];

運(yùn)行結(jié)果:

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

可以看到 當(dāng)子類實(shí)現(xiàn)initialize方法后,會覆蓋父類initialize方法,這一點(diǎn)和繼承思想一樣

我們在Person.m和Person+Category.m中實(shí)現(xiàn)initialize方法

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

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

調(diào)用Person 的 new方法,運(yùn)行

[Person new];

運(yùn)行結(jié)果:

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

運(yùn)行后可以看到Person的initialize方法并沒有被執(zhí)行,已經(jīng)被Person+Category中的initialize取代了

當(dāng)有多個Category時會怎樣了,我們在Person,Person+Category,Person+Category2,Person+Category3 的.m中都實(shí)現(xiàn)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__);
}

調(diào)用Person new方法,運(yùn)行

[Person new];

運(yùn)行結(jié)果:

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

可以看到,當(dāng)存在多個Category時,也只執(zhí)行一個,具體執(zhí)行哪一個Category中的initialize方法,測試后便可發(fā)現(xiàn),會執(zhí)行Compile Sources 列表中最后一個Category 的initialize方法

Compile Sources.png

什么情況下使用:

+load

由于調(diào)用load方法時的環(huán)境很不安全轴或,我們應(yīng)該盡量減少load方法的邏輯。另一個原因是load方法是線程安全的仰禀,它內(nèi)部使用了鎖照雁,所以我們應(yīng)該避免線程阻塞在load方法中

load很常見的一個使用場景,交換兩個方法的實(shí)現(xiàn)

//摘自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方法主要用來對一些不方便在編譯期初始化的對象進(jìn)行賦值。比如NSMutableArray這種類型的實(shí)例化依賴于runtime的消息發(fā)送答恶,所以顯然無法在編譯器初始化:


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

總結(jié):

load和initialize的共同點(diǎn)

1.如果父類和子類都被調(diào)用,父類的調(diào)用一定在子類之前

+load方法要點(diǎn)

當(dāng)類被引用進(jìn)項(xiàng)目的時候就會執(zhí)行l(wèi)oad函數(shù)(在main函數(shù)開始執(zhí)行之前),與這個類是否被用到無關(guān),每個類的load函數(shù)只會自動調(diào)用一次.由于load函數(shù)是系統(tǒng)自動加載的饺蚊,因此不需要再調(diào)用[super load],否則父類的load函數(shù)會多次執(zhí)行悬嗓。

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

注意:
load調(diào)用時機(jī)比較早,當(dāng)load調(diào)用時,其他類可能還沒加載完成,運(yùn)行環(huán)境不安全.
load方法是線程安全的污呼,它使用了鎖,我們應(yīng)該避免線程阻塞在load方法.


+initialize方法要點(diǎn)

initialize在類或者其子類的第一個方法被調(diào)用前調(diào)用包竹。即使類文件被引用進(jìn)項(xiàng)目,但是沒有使用,initialize不會被調(diào)用燕酷。由于是系統(tǒng)自動調(diào)用,也不需要顯式的調(diào)用父類的initialize周瞎,否則父類的initialize會被多次執(zhí)行苗缩。假如這個類放到代碼中,而這段代碼并沒有被執(zhí)行声诸,這個函數(shù)是不會被執(zhí)行的酱讶。

  • 1.父類的initialize方法會比子類先執(zhí)行
  • 2.當(dāng)子類不實(shí)現(xiàn)initialize方法,會把父類的實(shí)現(xiàn)繼承過來調(diào)用一遍彼乌。在此之前泻肯,父類的方法會被優(yōu)先調(diào)用一次
  • 3.當(dāng)有多個Category都實(shí)現(xiàn)了initialize方法,會覆蓋類中的方法,只執(zhí)行一個(會執(zhí)行Compile Sources 列表中最后一個Category 的initialize方法)

注意:
在initialize方法收到調(diào)用時,運(yùn)行環(huán)境基本健全渊迁。
initialize內(nèi)部也使用了鎖,所以是線程安全的灶挟。但同時要避免阻塞線程宫纬,不要再使用鎖


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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市膏萧,隨后出現(xiàn)的幾起案子漓骚,更是在濱河造成了極大的恐慌,老刑警劉巖榛泛,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蝌蹂,死亡現(xiàn)場離奇詭異,居然都是意外死亡曹锨,警方通過查閱死者的電腦和手機(jī)孤个,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來沛简,“玉大人齐鲤,你說我怎么就攤上這事〗烽梗” “怎么了给郊?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長捧灰。 經(jīng)常有香客問我淆九,道長,這世上最難降的妖魔是什么毛俏? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任炭庙,我火速辦了婚禮,結(jié)果婚禮上煌寇,老公的妹妹穿的比我還像新娘焕蹄。我一直安慰自己,他們只是感情好阀溶,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布腻脏。 她就那樣靜靜地躺著,像睡著了一般淌哟。 火紅的嫁衣襯著肌膚如雪迹卢。 梳的紋絲不亂的頭發(fā)上辽故,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天徒仓,我揣著相機(jī)與錄音,去河邊找鬼誊垢。 笑死掉弛,一個胖子當(dāng)著我的面吹牛症见,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播殃饿,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼谋作,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了乎芳?” 一聲冷哼從身側(cè)響起遵蚜,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎奈惑,沒想到半個月后吭净,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡肴甸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年寂殉,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片原在。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡友扰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出庶柿,到底是詐尸還是另有隱情村怪,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布浮庐,位于F島的核電站实愚,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏兔辅。R本人自食惡果不足惜腊敲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望维苔。 院中可真熱鬧碰辅,春花似錦、人聲如沸介时。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽沸柔。三九已至循衰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間褐澎,已是汗流浹背会钝。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人迁酸。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓先鱼,卻偏偏與公主長得像,于是被迫代替她去往敵國和親奸鬓。 傳聞我的和親對象是個殘疾皇子焙畔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容