Objective-C作為一門面向?qū)ο笳Z言勾栗,有類和對象的概念围俘。所有的代碼在執(zhí)行編譯后,類相關(guān)的數(shù)據(jù)結(jié)構(gòu)會保留在目標(biāo)文件中界牡,在運(yùn)行時(shí)得到解析和使用宿亡。在應(yīng)用程序運(yùn)行起來的時(shí)候,類的信息會有加載和初始化過程挽荠。類的加載和初始化過程中,就使用到了load
和initialize
兩個(gè)類方法漠另;而在NSObject
根類中,前兩個(gè)方法也是load
和initialize
兩個(gè)類方法性湿,本文就對這兩個(gè)特殊方法的區(qū)別與聯(lián)系满败、使用場景和注意事項(xiàng)做一個(gè)詳細(xì)的記錄。
+ (void)load;
+ (void)initialize;
一宵荒、load方法
顧名思義米同,load
方法在這個(gè)文件被程序裝載時(shí)調(diào)用。只要是在Compile Sources
中出現(xiàn)的文件總是會被裝載少孝,這與這個(gè)類是否被用到無關(guān)熬苍。在一個(gè)程序開始運(yùn)行之前(在main
函數(shù)開始執(zhí)行之前),類文件開始被程序加載婿脸,load
方法就會開始被執(zhí)行柄驻;因此load
方法總是在main
函數(shù)之前調(diào)用。
調(diào)用規(guī)則
當(dāng)父類和子類都實(shí)現(xiàn)load
方法時(shí)抑钟,父類的load
方法會被先執(zhí)行野哭。load
方法是系統(tǒng)自動(dòng)加載的,因此不需要使用[spur load]
方法調(diào)用父類的load
方法蛔溃,否則父類的load
方法會多次執(zhí)行。在Category中寫load
方法是不會替換原始類中的load
方法的徽曲,原始類和Category中的load
方法都會被執(zhí)行麸塞,原始類的load
方法會先被執(zhí)行,再執(zhí)行Category中的load
方法。當(dāng)有多個(gè)Category都實(shí)現(xiàn)了load
方法绍撞,在Compile Sources中文件的排放順序就是這幾個(gè)load
方法裝載順序傻铣。特別注意的是:如果一個(gè)類沒有實(shí)現(xiàn)load
方法,那么就不會調(diào)用它父類的load
方法非洲。
//In Parent.m
+ (void)load {
NSLog(@"Load Class Parent");
}
//In Child.m两踏,繼承自Parent
+ (void)load {
NSLog(@"Load Class Child");
}
//In Child+load.m,Child類的分類
+ (void)load {
NSLog(@"Load Class Child+load");
}
//In Child+load1.m梦染,Child類的分類
+ (void)load {
NSLog(@"Load Class Child+load1");
}
//In Child+load2.m帕识,Child類的分類
+ (void)load {
NSLog(@"Load Class Child+load2");
}
運(yùn)行結(jié)果:
2018-02-05 11:02:38.404 LoadDemo[3640:771656] Load Class Person
2018-02-05 11:02:38.405 LoadDemo[3640:771656] Load Class Parent
2018-02-05 11:02:38.405 LoadDemo[3640:771656] Load Class Child
2018-02-05 11:02:38.405 LoadDemo[3640:771656] Load Class Child+load
2018-02-05 11:02:38.405 LoadDemo[3640:771656] Load Class Child+load1
2018-02-05 11:02:38.405 LoadDemo[3640:771656] Load Class Child+load2
對于load方法執(zhí)行順序需要注意的地方
查看項(xiàng)目的Compile Sources配置,如下圖:
在上文中也提到過晶姊,在Compile Sources中文件的排放順序就是load
方法裝載順序伪货。從打印日志來看超歌,Parent
的load
方法先于Child
調(diào)用,而它在Compile Sources中的順序其實(shí)是在Child
之后巍举。這也證明了系統(tǒng)會在子類的load
方法中自動(dòng)調(diào)用父類的load
的方法。load
方法調(diào)用時(shí)蜓谋,系統(tǒng)處于脆弱狀態(tài),如果調(diào)用別的類的方法剑肯,且該方法依賴于那個(gè)類的load
方法進(jìn)行初始化設(shè)置观堂,那么必須確保那個(gè)類的load
方法已經(jīng)調(diào)用了,比如demo中的這段代碼溃睹,打印出的字符串就為null
胰坟。雖然在這種簡單情況下我們可以辨別出各個(gè)類的load
方法調(diào)用的順序,但永遠(yuǎn)不要依賴這個(gè)順序完成你的代碼邏輯竞滓,這在后期的開發(fā)中極容易導(dǎo)致錯(cuò)誤吹缔。
//In Child.m
+ (void)load
{
NSLog(@"Load Class Child");
Other *other = [Other new];
[other originalMethod];
//如果不先調(diào)用other的load,下面這行代碼就無效莉御,打印出null
[Other printNameMethod];
}
使用場景
由于load
方法調(diào)用時(shí)俗冻,我們應(yīng)該盡量減少load
方法內(nèi)的邏輯迄薄,一方面系統(tǒng)處于脆弱狀態(tài)而且很不安全,另一方面是load
方法是線程安全的讥蔽,它內(nèi)部使用了鎖,所以我們應(yīng)該避免線程阻塞在load
方法中新症。
一個(gè)最常見的使用場景是在load
方法中實(shí)現(xiàn)Method Swizzle功能:
//In Other.m
+ (void)load
{
Method originalMethod = class_getInstanceMethod([self class], @selector(originalMethod));
Method swizzledMethod = class_getInstanceMethod([self class], @selector(swizzledMethod));
method_exchangeImplementations(originalMethod, swizzledMethod);
}
在Child類的load方法中徒爹,由于還沒調(diào)用Other的load方法,所以輸出結(jié)果是"Original Output"隆嗅,而在main函數(shù)中胖喳,輸出結(jié)果自然就變成了"Swizzled Output"。一般來說丽焊,除了Method Swizzle技健,別的邏輯都不應(yīng)該放在load方法中實(shí)現(xiàn)。
二凫乖、initialize方法
initialize
方法在第一次給類發(fā)送消息時(shí)觸發(fā)(比如實(shí)例化一個(gè)對象)帽芽,并且只會調(diào)用一次翔冀。initialize
方法實(shí)際上是一種懶加載調(diào)用方式,也就是說如果一個(gè)類一直沒被用到搬瑰,那它的initialize
方法也不會被調(diào)用控硼。
調(diào)用規(guī)則
與load
方法類似的是,initialize
方法也是系統(tǒng)自動(dòng)加載的翼悴,因此也同樣不需要使用[spur initialize]
方法調(diào)用父類的initialize
方法幔妨。與load
方法不同之處在于,即使子類沒有實(shí)現(xiàn)initialize
方法古话,也會調(diào)用父類的方法锁施,這會導(dǎo)致一個(gè)很嚴(yán)重的問題:
//In Parent.m
+ (void)initialize
{
NSLog(@"Initialize Parent, caller Class %@", [self class]);
}
//In Child.m
//注釋掉initialize方法
//In main.m
Child *child = [Child new];
運(yùn)行后杖们,查看打印日志胀莹,會發(fā)現(xiàn)父類的initialize
方法竟然調(diào)用了兩次:
2018-02-05 11:02:38.804 LoadDemo[3640:771656] Initialize Parent, Parent caller
2018-02-05 11:02:38.804 LoadDemo[3640:771656] Initialize Parent, Child caller
這是因?yàn)樵趧?chuàng)建子類對象時(shí)婚温,首先要?jiǎng)?chuàng)建父類對象,所以會調(diào)用一次父類的initialize
方法荆秦,然后創(chuàng)建子類時(shí)力图,盡管自己沒有實(shí)現(xiàn)initialize
方法,但還是會調(diào)用到父類的方法瓤介。雖然initialize
方法對一個(gè)類而言只會調(diào)用一次赘那,但這里由于出現(xiàn)了兩個(gè)類,所以調(diào)用兩次符合規(guī)則祠斧,但不符合我們的需求拱礁。正確使用initialize
方法的姿勢如下:
//In Parent.m
+ (void)initialize
{
if (self == [Parent class]) {
NSLog(@"Initialize Parent, caller Class %@", [self class]);
}
}
加上判斷后,就不會因?yàn)樽宇惗{(diào)用到自己的initialize
方法了吴超。
使用場景
initialize
方法主要用來對一些不方便在編譯期初始化的對象進(jìn)行賦值鸯乃。比如說:無法在編譯期設(shè)定的全局變量飒责,可以放在initialize方法中初始化。
總結(jié)
-
load
和initialize
方法都會在實(shí)例化對象之前調(diào)用宏蛉,以main函數(shù)為分水嶺拾并,前者在main函數(shù)之前調(diào)用鹏浅,后者在之后調(diào)用屏歹。這兩個(gè)方法會被自動(dòng)調(diào)用,不能手動(dòng)調(diào)用它們季希。 -
load
和initialize
方法都不用顯示的調(diào)用父類的方法而是自動(dòng)調(diào)用幽纷,即使子類沒有initialize
方法也會調(diào)用父類的方法,而load
方法則不會調(diào)用父類峰尝。 -
load
方法通常用來進(jìn)行Method Swizzle收恢,initialize
方法一般用于初始化全局變量或靜態(tài)變量伦意。 -
load
和initialize
方法內(nèi)部使用了鎖,因此它們是線程安全的默赂。實(shí)現(xiàn)時(shí)要盡可能保持簡單缆八,避免阻塞線程疾捍,不要再使用鎖。 - 將針對于類修改放在
intialize
中奖恰,將針對Category的修改放在load
中宛裕。 -
load
與initialize
都應(yīng)該實(shí)現(xiàn)得精簡,有助于保持程序的響應(yīng)能力蛹屿。