load 和 initialize 兩個(gè)方法算是兩個(gè)特殊的類(lèi)方法了,今天偶然從草稿箱中看到還有本篇未完成的博文,如果說(shuō)當(dāng)初了解它們是為了應(yīng)付面試,那么工作之后,了解它們就變成了“必修課”,相比于網(wǎng)上某些十分官方的說(shuō)法,個(gè)人還是喜歡用大白話(huà)來(lái)詮釋自己對(duì)它們的理解,這里就寫(xiě)一下個(gè)人對(duì)這兩個(gè)方法的理解,如果有理解不到位的地方,還請(qǐng)指正,3Q
load方法
個(gè)人理解
從方法的名字來(lái)看,它應(yīng)該是在一個(gè)類(lèi)進(jìn)行裝載的時(shí)候觸發(fā),更果斷一點(diǎn)的說(shuō)法就是不管這個(gè)類(lèi)有沒(méi)有被調(diào)用,只要它被裝載,那么它就會(huì)運(yùn)行這個(gè)方法。
那么它什么時(shí)候才會(huì)被裝載呢?而這個(gè)時(shí)間點(diǎn)應(yīng)該就是這個(gè)類(lèi)文件在被第一次編譯的時(shí)候.
個(gè)人理解的編譯其實(shí)就是由編譯器靜態(tài)的分析語(yǔ)法等是否符合標(biāo)準(zhǔn)的過(guò)程并將符合標(biāo)準(zhǔn)的語(yǔ)法翻譯成機(jī)器語(yǔ)言(這也就是我們當(dāng)語(yǔ)法不對(duì)的時(shí)候會(huì)出現(xiàn)報(bào)錯(cuò),這個(gè)階段是靜態(tài)的,如果這里有些值是需要?jiǎng)討B(tài)確定的,強(qiáng)烈建議請(qǐng)換個(gè)地方初始化吧)。
示例
如果還是比較抽象,這里就用代碼創(chuàng)建幾個(gè)類(lèi)來(lái)說(shuō)明一下,以下創(chuàng)建一個(gè)叫做Person的類(lèi)作為基類(lèi),在創(chuàng)建一個(gè)Student類(lèi)繼承自Person類(lèi),同時(shí)寫(xiě)一個(gè)Student的類(lèi)別,并分別實(shí)現(xiàn)它們的load方法:
//Person.m
+(void)load { NSLog(@"I am Person..Load Function!"); }
//Student.m 繼承自Person
+(void)load { NSLog(@"I am Student..Load Function!"); }
//Student+CustomLoad.m
+(void)load { NSLog(@"I am Student..Load Function!"); }
此時(shí)我們?cè)趍ain.m文件不做任何的操作,編譯通過(guò)后運(yùn)行,打印結(jié)果如下:
2016-08-30 14:36:59.037 Load_Initalization_Test[25633:319076] I am Person..Load Function!
2016-08-30 14:36:59.038 Load_Initalization_Test[25633:319076] I am Student..Load Function!
2016-08-30 14:36:59.038 Load_Initalization_Test[25633:319076] I am Student+CustomLoad..Load Function!
Program ended with exit code: 0
盡管我沒(méi)有調(diào)用它們,但是它們load方法執(zhí)行了,這就應(yīng)了之前說(shuō)的話(huà),不管類(lèi)有沒(méi)有被調(diào)用,只要編譯到就會(huì)執(zhí)行l(wèi)oad方法。
那么我們?cè)趺粗牢募?huì)不會(huì)編譯呢,其實(shí)在創(chuàng)建這個(gè)類(lèi)的時(shí)候,Xcode自動(dòng)已經(jīng)幫我們把它添加到了Compile Sources里面(這里面的實(shí)現(xiàn)文件會(huì)在編譯器編譯階段進(jìn)行加載,也就是我們所說(shuō)的load),具體位置就在Targets->Build Phases->Compile Source里面。
如下圖(當(dāng)然,如果把Student+CustomLoad.m從里面刪掉,那么就不會(huì)打印Student+CustomLoad..Load Function!
這句了;但如果您想把Person.m去掉,那么編譯器是不會(huì)讓你通過(guò),因?yàn)镾tudent的load依賴(lài)于父類(lèi)Person)
執(zhí)行順序
那么順序?yàn)槭裁磍oad方法會(huì)是Person優(yōu)先,其次是Student,最后才是Student+CustomLoad呢,load的開(kāi)發(fā)文檔中有這么兩句話(huà)直接的闡明了它們的調(diào)用順序(盡管我相信大家能看得懂,但在后面還是簡(jiǎn)單的翻譯了一下):
- A class’s +load method is called after all of its superclasses’ +load methods.
- 一個(gè)類(lèi)的load方法是在它所有的父類(lèi)之后執(zhí)行
- A category +load method is called after the class’s own +load method.
- 一個(gè)類(lèi)別的load方法是在自己的load方法之后執(zhí)行
總結(jié)
- load方法不需要(不需要并不是說(shuō)不能哦)使用[super load]來(lái)顯性的調(diào)用父類(lèi)的load方法,只要被添加到編譯源下面就會(huì)執(zhí)行。
- 不管子類(lèi)有沒(méi)有寫(xiě)load方法,父類(lèi)的load都只會(huì)執(zhí)行一次(說(shuō)這句只是為了區(qū)別下面的initialize方法)
- load方法執(zhí)行的時(shí)候,系統(tǒng)為脆弱狀態(tài),如果我們?cè)趌oad里面需要調(diào)用其它類(lèi)的實(shí)例對(duì)象(或類(lèi)對(duì)象)的屬性或者方法,必須要確保那個(gè)依賴(lài)類(lèi)(這個(gè)依賴(lài)類(lèi)可不是之前說(shuō)的父類(lèi))的load方法執(zhí)行完畢(個(gè)人看法: 這種牽扯到稍微帶點(diǎn)邏輯的東西就不要在里面實(shí)現(xiàn)了,使用場(chǎng)景↓。
使用場(chǎng)景
load方法是線(xiàn)程安全的,內(nèi)部已經(jīng)使用了鎖,一般的應(yīng)用場(chǎng)景是在該方法中實(shí)現(xiàn)方法的交換(Method Swizzle (runtime的黑魔法)),比如我們對(duì)Person和Student新增兩個(gè)方法如下:
//Person.m
-(void)personSay { NSLog(@"I am a Person"); }
//Student.m
-(void)studentSay { NSLog(@"I am a Student"); }
//Student.m load方法里面對(duì)方法實(shí)現(xiàn)進(jìn)行交換
+(void)load
{
//實(shí)現(xiàn)personSay與studentSay方法的交換
Method personMethod = class_getInstanceMethod([Person class], NSSelectorFromString(@"personSay"));
Method studentMethod = class_getInstanceMethod([Student class], NSSelectorFromString(@"studentSay"));
method_exchangeImplementations(personMethod, studentMethod);
}
//這個(gè)時(shí)候我們?cè)趍ain.m的函數(shù)里面使用student實(shí)例對(duì)象調(diào)用方法studentSay,打印結(jié)果為I am a Person
initialize方法
個(gè)人理解
這個(gè)方法是當(dāng)某個(gè)類(lèi)第一次收到消息的時(shí)候觸發(fā)(如果稍微有一點(diǎn)點(diǎn)看過(guò)runtime的話(huà),應(yīng)該會(huì)知道ObjC中的調(diào)用方法本質(zhì)就是消息的傳遞)。
以下是個(gè)人的理解,如果有錯(cuò)誤請(qǐng)指出:
1、可以當(dāng)成一個(gè)單例方法,只不過(guò)與之前所理解的單例方法稍有不同,這里初始化的不是一個(gè)實(shí)例對(duì)象,而是一個(gè)類(lèi)對(duì)象,因?yàn)轭?lèi)對(duì)象其實(shí)就是一個(gè)單例對(duì)象嘛,我們可以釋放實(shí)例對(duì)象,但誰(shuí)能釋放一個(gè)類(lèi)對(duì)象給樓主瞧一瞧呢(可能這句話(huà)說(shuō)的有點(diǎn)自大,也可能真的有辦法,但只是為了好理解才這么說(shuō)的,大神這里就不要挑刺,不過(guò)具體方法樓主還是很感興趣滴,求告知呀)。
2、如果沒(méi)有用到該類(lèi),就算加載完畢也不會(huì)執(zhí)行該方法(這點(diǎn)與load方法不同,load方法是只要加載就執(zhí)行,initialize方法必須是第一次使用該類(lèi)的時(shí)候才觸發(fā),如果按照l(shuí)oad的那個(gè)實(shí)例,那么不會(huì)執(zhí)行initialize方法)
3、如果對(duì)類(lèi)對(duì)象的理解有點(diǎn)模糊,那么就舉個(gè)例子:如果說(shuō)在調(diào)用實(shí)例方法(-)的時(shí)候必須要有一個(gè)實(shí)例對(duì)象,那么在調(diào)用類(lèi)方法(+)的時(shí)候是不是也需要有一個(gè)類(lèi)對(duì)象呢,比如alloc
以及new
,這兩個(gè)類(lèi)方法用的算是最多的了吧..如果沒(méi)有初始化一個(gè)類(lèi)對(duì)象,請(qǐng)調(diào)用一個(gè)我看看O(∩_∩)O
示例
如果上面的一系列文字更讓人摸不著頭腦的話(huà),我覺(jué)得下面的示例代碼會(huì)讓思路更加清晰一下:
初始化父類(lèi)對(duì)象并只實(shí)現(xiàn)父類(lèi)的initialize方法
實(shí)現(xiàn)Person類(lèi)的initialize初始化方法
//Person.m
+(void)initialize { NSLog(@"I am Person..initialize Function!"); }
這里與load方法不同,在main函數(shù)需要調(diào)用一下Person類(lèi),這里就打印了一下Person的類(lèi)型
int main(int argc, const char * argv[]) {
@autoreleasepool {
//打印Person的類(lèi)型,當(dāng)然大家肯定知道他是Person類(lèi)啦
NSLog(@"Person Class = %@",NSStringFromClass([Person class]));
}
return 0;
}
看一下結(jié)果吧:
//優(yōu)先調(diào)用了initialize方法
2016-09-01 14:58:42.228 Load_Initalization_Test[5012:313370] I am Person..initialize Function!
2016-09-01 14:58:42.229 Load_Initalization_Test[5012:313370] Person Class = Person
按照個(gè)人的理解解釋一下為什么會(huì)優(yōu)先調(diào)用initialize方法的原因吧:
在main方法里我們調(diào)用了Person的一個(gè)類(lèi)方法,沒(méi)錯(cuò),就是[Person class]
,有沒(méi)有想到一句話(huà)呢?第一次使用類(lèi)發(fā)送消息之前調(diào)用的方法,這里的調(diào)用class方法的時(shí)候是第一次使用Person類(lèi),所以調(diào)用了Person的initialize方法。
初始化子類(lèi)對(duì)象并同時(shí)實(shí)現(xiàn)子類(lèi)和父類(lèi)的initialize方法
實(shí)現(xiàn)方法如下:
//Person.m
+(void)initialize { NSLog(@"I am Person..initialize Function!"); }
//Student.m 繼承自Person類(lèi)
+(void)initialize { NSLog(@"I am a Student..initialize Function!"); }
這里就不在main函數(shù)里面打印Person的類(lèi)型了,我們打印一下Student的類(lèi)型吧
//簡(jiǎn)單的一句打印
NSLog(@"Student Class = %@",NSStringFromClass([Student class]));
打印結(jié)果如下:
//這個(gè)時(shí)候發(fā)現(xiàn)Student的父類(lèi)Person的initialize方法優(yōu)先
2016-09-01 15:12:11.846 Load_Initalization_Test[5154:326709] I am Person..initialize Function!
//Student的initialize其次,至于為什么優(yōu)先執(zhí)行父類(lèi)的方法,下面會(huì)有執(zhí)行順序的一個(gè)描述,這個(gè)實(shí)例不是重點(diǎn)
2016-09-01 15:12:11.847 Load_Initalization_Test[5154:326709] I am a Student..initialize Function!
//最后打印的才是Student的類(lèi)型
2016-09-01 15:12:11.847 Load_Initalization_Test[5154:326709] Student Class = Student
初始化子類(lèi)對(duì)象并只實(shí)現(xiàn)父類(lèi)的initialize方法
代碼就不貼了,只要把Student.m中的initialize完全注釋掉即可,打印結(jié)果如下:
//父類(lèi)的initialize方法執(zhí)行了一次,意料之中
2016-09-01 15:16:40.307 Load_Initalization_Test[5222:331121] I am Person..initialize Function!
//我去,這是什么鬼,為啥又執(zhí)行了一次
2016-09-01 15:16:40.308 Load_Initalization_Test[5222:331121] I am Person..initialize Function!
2016-09-01 15:16:40.308 Load_Initalization_Test[5222:331121] Person Class = Student
這也就是在總結(jié)load的時(shí)候有這么一句不管子類(lèi)有沒(méi)有寫(xiě)load方法,父類(lèi)的load都只會(huì)執(zhí)行一次
,而initialize的方法不同,如果子類(lèi)的initialize沒(méi)有實(shí)現(xiàn),那么它就會(huì)繼續(xù)執(zhí)行一遍父類(lèi)的initialize方法,為什么會(huì)出現(xiàn)這種情況,請(qǐng)看下面的執(zhí)行順序↓;如果的執(zhí)行順序會(huì)有什么問(wèn)題呢,請(qǐng)看后面的使用場(chǎng)景。
執(zhí)行順序
下面是開(kāi)發(fā)文檔中節(jié)選的那么幾段
- Superclasses receive this message before their subclasses.
- 父類(lèi)會(huì)在子類(lèi)之前收到這個(gè)消息
- The superclass implementation may be called multiple times if subclasses do not implement initialize
- 如果子類(lèi)沒(méi)有實(shí)現(xiàn)這個(gè)方法,那么父類(lèi)的實(shí)現(xiàn)將會(huì)被執(zhí)行數(shù)次
總結(jié)
- initialize方法也不需要使用[super initialize]來(lái)顯性調(diào)用父類(lèi)的initialize方法,只有第一次調(diào)用該類(lèi)的時(shí)候才會(huì)觸發(fā)。
- 如果子類(lèi)實(shí)現(xiàn)了initialize方法,那么初始化時(shí)各自執(zhí)行各自的initialize方法,如果子類(lèi)沒(méi)有實(shí)現(xiàn)initialize方法,那么就會(huì)自動(dòng)調(diào)用父類(lèi)的initialize方法。
- initialize方法也是在一個(gè)安全線(xiàn)程中,也不需要編寫(xiě)復(fù)雜邏輯的代碼。
使用場(chǎng)景
runtime向這個(gè)類(lèi)發(fā)送初始化消息的時(shí)候是線(xiàn)程安全的,所以也不需要在這個(gè)方法里面添加太復(fù)雜的邏輯,萬(wàn)一死鎖呢,通常我們會(huì)在這里面對(duì)靜態(tài)變量進(jìn)行初始化,比如:
static NSString * personName;
@implementation Person
//實(shí)現(xiàn)
+(void)initialize
{
NSLog(@"I am Person..initialize Function!");
personName = @"RITL";
}
這樣的話(huà),如果調(diào)用Person子類(lèi)的時(shí)候,沒(méi)有寫(xiě)initialize方法,豈不是要對(duì)這個(gè)靜態(tài)變量初始化N次,為了防止這種情況的發(fā)生,可以寫(xiě)成如下:
+(void)initialize
{
//判斷一下當(dāng)前的類(lèi)型,當(dāng)然不止這一種寫(xiě)法
//比如if([NSStringFromClass([self class]) isEqualToString:@"Person"])也是可以的
if (self == [Person self])
{
NSLog(@"I am Person..initialize Function!");
personName = @"RITL";
}
}
這個(gè)時(shí)候打印一下,結(jié)果如下:
//只執(zhí)行了一次,解決初始化多次的問(wèn)題
2016-09-01 15:31:39.973 Load_Initalization_Test[5415:348083] I am Person..initialize Function!
2016-09-01 15:31:39.974 Load_Initalization_Test[5415:348083] Person Class = Student
Super 關(guān)鍵詞
這里為什么要追加一下super"關(guān)鍵詞"
呢,注意,這里說(shuō)super是一個(gè)關(guān)鍵詞,而不是很多人理解的父類(lèi)!!
我們知道的是,當(dāng)使用super關(guān)鍵詞調(diào)用方法的時(shí)候是不會(huì)執(zhí)行本類(lèi)的方法,而是調(diào)用父類(lèi)的方法。下面的實(shí)例應(yīng)該都是懂得,比如:
//調(diào)用本類(lèi)的say方法
[self say];
//調(diào)用父類(lèi)的say方法
[super say];
super的作用:使用super關(guān)鍵詞調(diào)用方法的時(shí)候,在runtime查找方法的實(shí)現(xiàn)時(shí)不會(huì)從當(dāng)前的類(lèi)的方法列表中查找,而是跳過(guò)本類(lèi)從父類(lèi)的方法列表中查找實(shí)現(xiàn)方法。盡管執(zhí)行的是父類(lèi)的方法,但是方法的調(diào)用者(消息的發(fā)送者)依舊是當(dāng)前類(lèi),這也就解釋了下面這段代碼
//在Student的initialize方法中如下調(diào)用
+(void)initialize
{
NSLog(@"super class = %@",NSStringFromClass([super class]));
NSLog(@"self class = %@",NSStringFromClass([self class]));
}
//打印結(jié)果(全是Student):
2016-09-02 09:01:23.737 Load_Initalization_Test[1259:49022] super class = Student
2016-09-02 09:01:23.737 Load_Initalization_Test[1259:49022] self class = Student
牛刀小試
小試
如果感覺(jué)自己理解了load和initialize方法的話(huà),不如來(lái)測(cè)試一下吧,說(shuō)出load(Person) initialize(Person) load(Student) initialize(Student)的順序吧:
//第一個(gè)呢:在子類(lèi)的Student.m的load方法中寫(xiě)法如下,Person的load方法不實(shí)現(xiàn):
+(void)load
{
[Student class];
[Person class];
}
//第二個(gè)呢:在父類(lèi)的Person.m的load方法中寫(xiě)法如下,Student的load方法不實(shí)現(xiàn)
+(void)load
{
[Student class];
[Person class];
}
順序結(jié)果以及解釋
- 第一個(gè)呢:load(Person) >> load(Student) >> initialize(Person) >> initalize(Student) (>>早于)
- 第一次調(diào)用Person以及Student類(lèi)對(duì)象是在Student 的load方法里面,那么父類(lèi)Person會(huì)首先收到執(zhí)行l(wèi)oad的消息,所以第一個(gè)執(zhí)行。
- 還是a的前綴,第一次調(diào)用Person以及Student類(lèi)對(duì)象是在Student 的load方法里面,所以Student的load方法是執(zhí)行在他們所有的initialize方法之前的。
- initialize方法的執(zhí)行順序就是父類(lèi)比子類(lèi)先執(zhí)行,所以最終順序就是load(Person) >> load(Student) >> initialize(Person) >> initalize(Student)
- 第二個(gè)呢:load(Person) >> initalize(Person) >> initialize(Student) >> load(Student) (>>早于)
- 第一次調(diào)用Person以及Student類(lèi)對(duì)象是在Person 的load方法里面,那么父類(lèi)Person會(huì)首先執(zhí)行l(wèi)oad的消息,所以第一個(gè)執(zhí)行。
- 在Person的load里面(這個(gè)時(shí)候Student的load方法是沒(méi)有執(zhí)行的,因?yàn)楸仨毜鹊礁割?lèi)的load方法執(zhí)行完畢之后才會(huì)執(zhí)行子類(lèi)的load方法,不信看上面的順序描述呀)。
- 在Person的load方法里面優(yōu)先調(diào)用了Student對(duì)象,但根據(jù)initalize方法的執(zhí)行順序,所以Person的initalize方法優(yōu)先,其次是Student的initalize方法