load與initialize方法

load與initialize方法總結(jié):

load:
1、不走objc_msgSend流程侵佃,根據(jù)load方法的地址直接調(diào)用麻昼,并且在執(zhí)行main函數(shù)之前調(diào)用。
2馋辈、按編譯順序依次遍歷類抚芦,看當(dāng)前類是否實現(xiàn)了load方法,如果實現(xiàn)了首有,那么在此之前會先看當(dāng)前類父類有沒有實現(xiàn)load方法燕垃,有則父類的load方法先調(diào)用。所有類的load方法執(zhí)行完畢后井联,按照編譯順序執(zhí)行所有分類的load方法卜壕。

注意:我們通常在load方法中進(jìn)行方法交換(Method Swizzle),除此之外烙常,除非真的有必要轴捎,我們盡量不要在load方法中寫代碼鹤盒,尤其不要在load方法中使用其它的類,因為這個時候其它的類可能還沒有被加載進(jìn)內(nèi)存侦副,隨意使用可能會出問題侦锯。如果確實要在load方法寫一些代碼,那也要盡量精簡代碼秦驯,不要做一些耗時或者等待鎖的操作尺碰,因為整個程序在執(zhí)行l(wèi)oad方法時都會阻塞,從而導(dǎo)致程序啟動時間過長甚至無法啟動译隘。

initialize:
1亲桥、與普通方法調(diào)用一樣,走的是objc_msgSend流程固耘,類或它的子類收到第一條消息時會調(diào)用题篷,這里的消息就是指實例方法或類方法的調(diào)用,所以所有類的initialize調(diào)用是在執(zhí)行main函數(shù)之后調(diào)用的厅目。
2番枚、如果一個類和它的分類都實現(xiàn)了initialize方法,那最終調(diào)用的會是分類中的方法损敷。如果子類和父類都實現(xiàn)了initialize方法葫笼,那么會先調(diào)用父類的方法,然后調(diào)用子類的方法(這里注意子類中不需要顯示調(diào)用父類的方法拗馒,通過查看源碼得知它是在底層實現(xiàn)過程中主動調(diào)用的父類的initialize方法)渔欢。
3、一個類只會調(diào)用一次initialize方法瘟忱,并且未被使用則不會調(diào)用。

注意:雖然使用initialize要比使用load安全(因為在調(diào)用initialize時所有類已經(jīng)被加載進(jìn)內(nèi)存了)苫幢,但我們還是要盡量少用initialize這個方法個访诱,尤其要謹(jǐn)慎在分類中實現(xiàn)initialize方法,因為如果在分類中實現(xiàn)了韩肝,本類實現(xiàn)的initialize方法將不會被調(diào)用触菜。實際開發(fā)中initialize方法一般用于初始化全局變量或靜態(tài)變量。

load與initialize方法

OC文件在編譯后哀峻,類相關(guān)的數(shù)據(jù)結(jié)構(gòu)會保留在目標(biāo)文件中涡相,在運行時得到解析和使用。在應(yīng)用程序運行起來的時候剩蟀,類的信息會有加載和初始化過程催蝗,這個過程就涉及到了類的兩個類方法:loadinitialize。下面我們就來介紹一下這2個方法的區(qū)別育特。(首先要說明一下丙号,這2個方法是系統(tǒng)調(diào)用的,開發(fā)者一般不會主動去調(diào)用者兩個方法,這么做也沒有什么意義犬缨,所以后面的講解都是針對系統(tǒng)調(diào)用喳魏,不考慮主動調(diào)用的情況)。

1. load方法

1.1 調(diào)用時機(jī)

當(dāng)我們啟動程序時怀薛,參與了編譯的類刺彩、分類都會被加載進(jìn)內(nèi)存,load方法就是在這個類被加載的時候調(diào)用的(前提是這個類有實現(xiàn)load方法)枝恋,這個過程與這個類是否被使用是無關(guān)的创倔,也就是說如果有一個類(MyClass)即使在整個程序中都沒有用到,甚至沒有任何一個文件去引入MyClass的頭文件鼓择,MyClass的的load的方法一樣會被調(diào)用三幻。等所有的類、分類都加載進(jìn)內(nèi)存后才會調(diào)用程序的main函數(shù)呐能,所以所有類的load方法都是在main函數(shù)之前被調(diào)用的念搬。而且每個類、分類的load方法只會被調(diào)用一次摆出。

1.2 調(diào)用順序

一個程序中如果所有的類朗徊、分類都實現(xiàn)了load方法,那么所有的load方法都會被調(diào)用偎漫。它們的執(zhí)行順序遵循以下規(guī)則:

  • 先執(zhí)行所有類的load方法爷恳,再執(zhí)行所有分類的load方法。

  • 執(zhí)行類的load方法時象踊,是按照參與編譯的順序温亲,先編譯的類先執(zhí)行,但是如果某個類是繼承自另一個類杯矩,那么會先執(zhí)行父類的load方法個再執(zhí)行自己的load方法栈虚。

  • 執(zhí)行分類的load方法時,是按照分類參與編譯的順序史隆,先編譯的分類先執(zhí)行魂务。

關(guān)于編譯順序,我們可以在項目的Build Phases --> Compile Sources查看泌射,最上面的就最先編譯粘姜,我們可以拖動文件來調(diào)整編譯順序。

下面舉個例子來看下load方法的執(zhí)行順序熔酷。首先說明一下幾個類的關(guān)系:Person類有aaabbb兩個分類孤紧,men類繼承自Person類,men也有2個分類cccddd纯陨,Book類和前面這些類沒有任何關(guān)系坛芽。

解釋:

  • 編譯順序從上到下留储,上面先編譯,下面后編譯咙轩。由于先執(zhí)行類的load再執(zhí)行分類的load获讳,最先參與編譯的類是men,而men繼承自Person活喊,所以最先執(zhí)行Personload(雖然Person是后參與編譯的丐膝,但是它是父類,所以會先執(zhí)行)钾菊,然后再執(zhí)行menload吧史。接著參與編譯的是Book類观游,所以緊接著就是執(zhí)行Bookload囤萤。再接著參與編譯的類就是Person猴鲫,由于它的load方法已經(jīng)執(zhí)行過了,此時就不會執(zhí)行了滞详。

  • 所有的類的load方法都執(zhí)行完后開始執(zhí)行分類的load凛俱,分類參與編譯的順序是men+ccc-->Person+aaa-->men+ddd-->Person+bbb,所以分類的load方法個也是按照這個順序執(zhí)行料饥。

1.3 執(zhí)行方式

我們知道蒲犬,當(dāng)分類中存在和本類中同名的方法時,調(diào)用這個方法最終執(zhí)行的是分類中的方法岸啡。那上面就很奇怪了原叮,PersonPerson的分類中都有load方法,按理說調(diào)用load方法時最終只會調(diào)用其中一個分類的load方法巡蘸,可結(jié)果Person本類和它的2個分類都調(diào)用了load方法奋隶。

這是因為load方法和普通方法調(diào)用的方式不一樣。普通方法調(diào)用是通過消息發(fā)送機(jī)制實現(xiàn)的悦荒,會先去類或元類的方法列表中查找达布,如果找到了方法就執(zhí)行,如果沒有找到就去父類的方法列表里面找逾冬,只要找到就會終止查找,所以只會執(zhí)行一次躺苦。

load方法調(diào)用時身腻,每個類都是根據(jù)load方法的地址直接調(diào)用,而不會走objc_msgSend函數(shù)的方法查找流程匹厘,也就是說一個類有實現(xiàn)load方法就執(zhí)行嘀趟,沒有就不執(zhí)行(沒有的話也不會去父類里面查找)。

想要了解更加詳細(xì)的底層實現(xiàn)流程愈诚,可以去看objc4源碼她按,

https://opensource.apple.com/tarballs/objc4/

這里提供一下相關(guān)函數(shù)調(diào)用流程以便進(jìn)行源碼閱讀: 首先從objc-os.mm文件的_objc_init函數(shù)開始-->load_images-->prepare_load_methods-->schedule_class_load-->add_class_to_loadable_list-->add_category_to_loadable_list-->call_load_methods-->call_class_loads-->call_category_loads-->(*load_method)(cls, SEL_load)牛隅。

1.4 實現(xiàn)load方法時要注意什么

我們通常在load方法中進(jìn)行方法交換(Method Swizzle),除此之外酌泰,除非真的有必要媒佣,我們盡量不要在load方法中寫代碼,尤其不要在load方法中使用其它的類陵刹,因為這個時候其它的類可能還沒有被加載進(jìn)內(nèi)存默伍,隨意使用可能會出問題。

如果確實要在load方法寫一些代碼衰琐,那也要盡量精簡代碼也糊,不要做一些耗時或者等待鎖的操作,因為整個程序在執(zhí)行load方法時都會阻塞羡宙,從而導(dǎo)致程序啟動時間過長甚至無法啟動狸剃。

2. initialize方法

2.1 調(diào)用時機(jī)

initialize方法是在類或它的子類收到第一條消息時被調(diào)用的,這里的消息就是指實例方法或類方法的調(diào)用狗热,所以所有類的initialize調(diào)用是在執(zhí)行main函數(shù)之后調(diào)用的钞馁。而且一個類只會調(diào)用一次initialize方法。如果一個類在程序運行過程中一直沒有被使用過斗搞,那這個類的initialize方法也就不會被調(diào)用指攒,這一點和load方法是不一樣的。

2.2 調(diào)用方式

initialize方法的調(diào)用和普通方法調(diào)用一樣僻焚,也是走的objc_msgSend流程允悦。所以如果一個類和它的分類都實現(xiàn)了initialize方法,那最終調(diào)用的會是分類中的方法虑啤。

如果子類和父類都實現(xiàn)了initialize方法隙弛,那么會先調(diào)用父類的方法,然后調(diào)用子類的方法(這里注意子類中不需要寫[super initialize]來調(diào)用父類的方法狞山,通過查看源碼得知它是在底層實現(xiàn)過程中主動調(diào)用的父類的initialize方法)全闷。

下面看一個例子:

父類Person實現(xiàn)了initializePersonSub1PersonSub2這兩個子類也實現(xiàn)了initialize萍启,PersonSub3PersonSub4這兩個子類沒有實現(xiàn)了initialize总珠,按照下面的順序?qū)嵗瘜ο螅?/p>

PersonSub1 *ps1 = [[PersonSub1 alloc] init];
Person *person = [[Person alloc] init];
PersonSub2 *ps2 = [[PersonSub2 alloc] init];
PersonSub3 *ps3 = [[PersonSub3 alloc] init];
PersonSub4 *ps4 = [[PersonSub4 alloc] init];

// ***************打印結(jié)果***************
2020-01-06 15:52:38.429218+0800 CommandLine[68706:7207027] +[Person initialize]
2020-01-06 15:52:38.429250+0800 CommandLine[68706:7207027] +[PersonSub1 initialize]
2020-01-06 15:52:38.429287+0800 CommandLine[68706:7207027] +[PersonSub2 initialize]
2020-01-06 15:52:38.429347+0800 CommandLine[68706:7207027] +[Person initialize]
2020-01-06 15:52:38.429380+0800 CommandLine[68706:7207027] +[Person initialize]

看到這個運行結(jié)果,有人就有疑問了:不是說一個類只會調(diào)用一次initialize方法嗎勘纯,為什么這里Personinitialize方法被調(diào)用了3次局服?

這里就需要講解一下底層源碼的執(zhí)行流程了,每個類都有一個標(biāo)記記錄這個類是否調(diào)用過initialize驳遵,我這里就用一個BOOL類型的isInitialized來表示淫奔,然后用selfClass來表示自己的類,用superClass來表示父類堤结,下面我用偽代碼來描述一下底層源碼執(zhí)行流程:

// 如果自己沒有調(diào)用過initialize就執(zhí)行里面的代碼
if(!selfClass.isInitialized){
    if(!superClass.isInitialized){
        // 如果父類沒有執(zhí)行過initialize就給父類發(fā)消息(一旦成功執(zhí)行initialize就將父類的isInitialized置為YES)
        objc_msgSend(superClass,@selector(initialize));
    }
    // 再給自己的類發(fā)消息(一旦成功執(zhí)行initialize就將自己類的isInitialized置為YES)
    objc_msgSend(selfClass,@selector(initialize));
}

復(fù)制代碼

接下來我來按照這個流程來解釋一下上面運行的結(jié)果:

  • 首先PersonSub1被使用唆迁,而此時PersonSub1isInitialized為NO鸭丛,而且父類PersonisInitialized也為NO,所以先給父類發(fā)消息執(zhí)行initialize唐责,執(zhí)行完后PersonisInitialized變?yōu)閅ES鳞溉。然后PersonSub1執(zhí)行自己的initialize,執(zhí)行完后PersonSub1isInitialized變?yōu)閅ES妒蔚。所以這一步先打印+[Person initialize]穿挨,然后打印+[PersonSub1 initialize]

  • 然后是Person實例化肴盏,此時PersonisInitialized為YES科盛,所以不會再調(diào)用initialize。所以這一步什么都沒打印菜皂。

  • 接著是PersonSub2實例化贞绵,此時PersonSub2isInitialized為NO,父類PersonisInitialized為YES恍飘,所以只有PersonSub2會執(zhí)行initialize榨崩,執(zhí)行完后PersonSub2isInitialized變?yōu)閅ES。所以這一步打印的是+[PersonSub2 initialize]章母。

  • 再接著是PersonSub3實例化母蛛,此時PersonSub3isInitialized為NO,父類PersonisInitialized為YES乳怎,所以只有PersonSub3會執(zhí)行initialize彩郊,但是由于PersonSub3沒有實現(xiàn)initialize,它就會去父類找這個方法的實現(xiàn)蚪缀,找到后就執(zhí)行父類Personinitialize(注意這里是PersonSub3執(zhí)行的Person中的initialize秫逝,而不是Person執(zhí)行的),執(zhí)行完后PersonSub3isInitialized變?yōu)閅ES询枚。所以這一步打印的是+[Person initialize]违帆。(注意這里打印的是方法信息,表示執(zhí)行的是Person中的initialize金蜀,而不是說是Person調(diào)用的initialize)刷后。

  • 最后是PersonSub4實例化,這一步過程和上面一步是一樣的渊抄,執(zhí)行完后PersonSub4isInitialized變?yōu)閅ES惠险。這一步打印的是+[Person initialize]

所以最后的結(jié)果就是Person抒线、PersonSub1PersonSub2渣慕、PersonSub3嘶炭、PersonSub4這5個類都執(zhí)行了一次initialize抱慌,雖然從運行結(jié)果來看Personinitialize執(zhí)行了3次,其實后面2次是PersonSub3PersonSub4調(diào)用的眨猎。

2.3 使用注意事項

雖然使用initialize要比使用load安全(因為在調(diào)用initialize時所有類已經(jīng)被加載進(jìn)內(nèi)存了)抑进,但我們還是要盡量少用initialize這個方法個,尤其要謹(jǐn)慎在分類中實現(xiàn)initialize方法睡陪,因為如果在分類中實現(xiàn)了寺渗,本類實現(xiàn)的initialize方法將不會被調(diào)用。實際開發(fā)中initialize方法一般用于初始化全局變量或靜態(tài)變量兰迫。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末信殊,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子汁果,更是在濱河造成了極大的恐慌涡拘,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件据德,死亡現(xiàn)場離奇詭異鳄乏,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)棘利,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門橱野,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人善玫,你說我怎么就攤上這事水援。” “怎么了蝌焚?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵裹唆,是天一觀的道長。 經(jīng)常有香客問我只洒,道長许帐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任毕谴,我火速辦了婚禮成畦,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘涝开。我一直安慰自己循帐,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布舀武。 她就那樣靜靜地躺著拄养,像睡著了一般。 火紅的嫁衣襯著肌膚如雪银舱。 梳的紋絲不亂的頭發(fā)上瘪匿,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天跛梗,我揣著相機(jī)與錄音,去河邊找鬼棋弥。 笑死核偿,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的顽染。 我是一名探鬼主播漾岳,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼粉寞!你這毒婦竟也來了尼荆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤仁锯,失蹤者是張志新(化名)和其女友劉穎耀找,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體业崖,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡野芒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了双炕。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片狞悲。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖妇斤,靈堂內(nèi)的尸體忽然破棺而出摇锋,到底是詐尸還是另有隱情,我是刑警寧澤站超,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布荸恕,位于F島的核電站,受9級特大地震影響死相,放射性物質(zhì)發(fā)生泄漏融求。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一算撮、第九天 我趴在偏房一處隱蔽的房頂上張望生宛。 院中可真熱鬧,春花似錦肮柜、人聲如沸陷舅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽莱睁。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間仰剿,已是汗流浹背耙箍。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留酥馍,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓阅酪,卻偏偏與公主長得像旨袒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子术辐,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

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