iOS開發(fā)中總能看到+load和+initialize的身影,網(wǎng)上對(duì)于這兩個(gè)方法有很多解釋,但有些細(xì)節(jié)不夠清楚,不夠詳細(xì)蔬充。今天我們來詳細(xì)扒一扒這兩個(gè)方法.
下面針對(duì)對(duì)load方法的使用過程的變現(xiàn)提出一些問題补疑。
問題
1.load方法什么時(shí)候調(diào)用欣喧?
2.load方法調(diào)用原理歼秽?(是消息機(jī)制還有另有別的機(jī)制)
3.load方法調(diào)用順序应役?
4.load方法調(diào)用次數(shù)?
新建代碼 新建Person類,Person+Test1類 箩祥,Person+Test2 類
Person類代碼如下:
.h
+ (void)test;
.m
+ (void)load
{
NSLog(@"Person +load");
}
+ (void)test
{
NSLog(@"Person +test");
}
Person+Test1類代碼如下:
.m
+ (void)load
{
NSLog(@"Person (Test1) +load");
}
+ (void)test
{
NSLog(@"Person (Test1) +test");
}
Person+Test2類代碼如下:
+ (void)load
{
NSLog(@"Person (Test2) +load");
}
+ (void)test
{
NSLog(@"Person (Test2) +test");
}
直接運(yùn)行代碼
//輸出
Person +load
Person (Test1) +load
Person (Test2) +load
上述代碼并沒有調(diào)用Person
這個(gè)類但是load方法還是執(zhí)行了
說明+load方法會(huì)在runtime加載類院崇、分類時(shí)調(diào)用
,通過iOS Category的本質(zhì)(一) 這篇文章,我們已經(jīng)知道,當(dāng)分類中和本類中存有相同的方法時(shí)袍祖,優(yōu)先調(diào)用分類中的方法底瓣,不調(diào)用本類中的方法,但是通過打印我們發(fā)現(xiàn)在本Demo中蕉陋,本類和兩個(gè)類別中的load方法都調(diào)用了捐凭,那么load方法的調(diào)用機(jī)制是什么呢?首先可以肯定的是不是消息機(jī)制
凳鬓,因?yàn)槿绻?code>消息機(jī)制就只會(huì)調(diào)用分類中的load方法茁肠,本類中的是不會(huì)調(diào)用的。
查看源碼 源碼下載地址
來到 _objc_init
找到 load_images
方法
找到 load_images
方法的實(shí)現(xiàn)
來到 prepare_load_methods
方法
在prepare_load_methods方法中缩举,分為兩個(gè)步驟:
1.獲取了所有類后,遍歷列表垦梆,將其中有+load方法的類加入loadable_class;
2.獲取所有的類別仅孩,遍歷列表托猩,將其中有+load方法的類加入loadable_categories.
來到 call_load_methods
方法
call_load_methods方法中,
1.調(diào)用類的load方法辽慕,在call_class_loads方法中通過在第一步讀取prepare_load_methods步驟里的loadable_classes,遍歷列表并調(diào)用+load方法京腥。
2.調(diào)用類別的+load方法。
3.處理異常溅蛉。
來到 call_class_loads
方法
(*load_method)(cls, SEL_load);
這段代碼也就是說+load方法的調(diào)用是通過直接使用函數(shù)內(nèi)存地址的方式實(shí)現(xiàn)的,而不是更常見的objc_msgSend來發(fā)送消息.
也正是這句代碼造就了+load方法的最大特點(diǎn):類,父類與分類之間+load方法的調(diào)用是互不影響的.也就是,子類不會(huì)主動(dòng)調(diào)用父類的+load方法,如果類與分類都實(shí)現(xiàn)了+load',那么兩個(gè)+load
方法都會(huì)被調(diào)用.`
來到 call_category_loads
方法
通過上述對(duì)源碼的解析 我們發(fā)現(xiàn)+(load)方法是根據(jù)方法地址直接調(diào)用绞旅,并不是講過objc_msgSend函數(shù)調(diào)用,并且當(dāng)調(diào)用 load 方法時(shí)是先調(diào)用本類中的load方法温艇,再調(diào)用分類中的load方法因悲。每個(gè)類的load方法只走一次
問題1 :當(dāng)存在繼承關(guān)系時(shí)是先調(diào)用子類的load方法還是父類的load方法,
新創(chuàng)建Student類繼承自Person
.m
+ (void)load
{
NSLog(@"Student +load");
}
輸出
Person +load
Student +load
Person (Test1) +load
Person (Test2) +load
可以看出當(dāng)存在繼承關(guān)系時(shí)默認(rèn)首先調(diào)用父類中l(wèi)oad方法勺爱,再調(diào)用子類中的load方法
從源碼中也可以看出這個(gè)處理晃琳。
來到數(shù)據(jù)處理方法中,方法查找順序如下: _objc_init load_images琐鲁, prepare_load_methods卫旱, schedule_class_load
在該方法中會(huì)首先通過schedule_class_load(cls->superclass)確保父類中的 +load方法被加入loadable_class(如果父類有+load方法的話),從而保證父類的+load方法總是在子類之前調(diào)用围段。
也因此顾翼,在覆寫+load方法時(shí),不需要調(diào)用super方法奈泪。
問題2 :當(dāng)存在兩個(gè)沒有繼承關(guān)系類時(shí)load方法的調(diào)用順序是怎樣的呢适贸?
新建Tree類繼承NSObject灸芳,代碼實(shí)現(xiàn)如下:
+ (void)load{
NSLog(@"樹生長的聲音 load調(diào)用");
}
來到工程中找到Build Settings 中,將Tree類的編譯順序挪動(dòng)到第一個(gè)位置
輸出
樹生長的聲音 load調(diào)用
Person +load
Student +load
Person (Test1) +load
Person (Test2) +load
load方法按照編譯先后順序調(diào)用(先編譯拜姿,先調(diào)用)
通過改變分類的編譯順序發(fā)現(xiàn)分類中的load方法也是按照編譯先后順序調(diào)用(先編譯烙样,先調(diào)用)
的原則。
通過上述代碼驗(yàn)證和對(duì)源碼的分析蕊肥,得到如下總結(jié):
+load方法會(huì)在runtime加載類谒获、分類時(shí)調(diào)用每個(gè)類、分類的+load壁却,在程序運(yùn)行過程中只調(diào)用一次
+(load)方法是根據(jù)方法地址直接調(diào)用批狱,并不是講過objc_msgSend函數(shù)調(diào)用
+load方法是在main函數(shù)之前調(diào)用的調(diào)用順序
沒有繼承關(guān)系,沒有添加類別的類展东,按照編譯先后順序調(diào)用(先編譯精耐,先調(diào)用)
存在繼承關(guān)系,并且添加類別的類琅锻,
先調(diào)用父類的+load卦停,再調(diào)用子類的+load,最后再調(diào)用分類的+load
+initialize方法詳解
首先也是提出幾個(gè)問題:
問題
- initialize方法什么時(shí)候調(diào)用恼蓬?
- initialize方法調(diào)用原理惊完?(是消息機(jī)制還有另有別的機(jī)制)
- initialize方法調(diào)用順序?
- initialize方法調(diào)用次數(shù)处硬?
創(chuàng)建Animal類小槐,代碼如下
#import "Animal.h"
@implementation Animal
+ (void)initialize{
NSLog(@"Animal +initialize");
}
@end
直接運(yùn)行程序,發(fā)現(xiàn)并沒有調(diào)用initialize方法
來到main函數(shù)中荷辕,實(shí)現(xiàn)代碼
int main(int argc, const char * argv[]) {
@autoreleasepool {
Animal *anim = [[Animal alloc]init];
}
return 0;
}
//輸出
Animal +initialize
結(jié)論:+initialize方法會(huì)在類第一次接收到消息時(shí)調(diào)用
問題:當(dāng)存在繼承關(guān)系時(shí)+initialize怎么調(diào)用
新建Cat類繼承于Animal凿跳,代碼實(shí)現(xiàn)如下:
#import "Cat.h"
@implementation Cat
+ (void)initialize{
NSLog(@"Cat +initialize");
}
@end
main函數(shù)修改代碼如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Cat *cat = [[Cat alloc]init];
}
return 0;
}
運(yùn)行程序輸出如下:
Animal +initialize
Cat +initialize
結(jié)論:先調(diào)用父類的+initialize方法,再調(diào)用子類的+initialize方法疮方,
問題:+initialize方法調(diào)用機(jī)制控嗜?
新建 Animal的類別 Animal+Text01和 Animal+Text02
代碼實(shí)現(xiàn)如下
// Animal+Text01
#import "Animal+Text01.h"
#import <AppKit/AppKit.h>
@implementation Animal (Text01)
+ (void)initialize{
NSLog(@"Animal+Text01 +initialize");
}
@end
// Animal+Text02
#import "Animal+Text02.h"
#import <AppKit/AppKit.h>
@implementation Animal (Text02)
+ (void)initialize{
NSLog(@"Animal+Text02 +initialize");
}
@end
輸出:
Animal+Text01 +initialize
結(jié)論:典型的 objc_msgSend函數(shù)調(diào)用方式
通過上述代碼驗(yàn)證,得到如下總結(jié):
+initialize方法會(huì)在類第一次接收到消息時(shí)調(diào)用
+initialize 消息發(fā)送機(jī)制(objc_msgSend)調(diào)用順序
先調(diào)用父類的+initialize,再調(diào)用子類的+initialize
(先初始化父類骡显,再初始化子類疆栏,每個(gè)類只會(huì)初始化1次)