iOS開發(fā)中總能看到+load和+initialize的身影,網(wǎng)上對(duì)于這兩個(gè)方法有很多解釋,官方也有說明,但有些細(xì)節(jié)不夠清楚,今天我們來詳細(xì)扒一扒這兩個(gè)方法.
load函數(shù)調(diào)用特點(diǎn)如下:
當(dāng)類被引用進(jìn)項(xiàng)目的時(shí)候就會(huì)執(zhí)行l(wèi)oad函數(shù)(在main函數(shù)開始執(zhí)行之前),與這個(gè)類是否被用到無關(guān),每個(gè)類的load函數(shù)只會(huì)自動(dòng)調(diào)用一次.由于load函數(shù)是系統(tǒng)自動(dòng)加載的春感,因此不需要調(diào)用父類的load函數(shù)弥喉,否則父類的load函數(shù)會(huì)多次執(zhí)行捷兰。
1.當(dāng)父類和子類都實(shí)現(xiàn)load函數(shù)時(shí),父類的load方法執(zhí)行順序要優(yōu)先于子類
2.當(dāng)子類未實(shí)現(xiàn)load方法時(shí),不會(huì)調(diào)用父類load方法
3.類中的load方法執(zhí)行順序要優(yōu)先于類別(Category)
4.當(dāng)有多個(gè)類別(Category)都實(shí)現(xiàn)了load方法,這幾個(gè)load方法都會(huì)執(zhí)行,但執(zhí)行順序不確定(其執(zhí)行順序與類別在Compile Sources中出現(xiàn)的順序一致)
5.當(dāng)然當(dāng)有多個(gè)不同的類的時(shí)候,每個(gè)類load 執(zhí)行順序與其在Compile Sources出現(xiàn)的順序一致
下面通過實(shí)例來一起驗(yàn)證下:
我們新建2個(gè)類:Person繼承NSObject,Student和Teacher均繼承Person
Person : NSObject
Student : Person
Teacher : Person
新建3個(gè)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方法時(shí)并不會(huì)調(diào)用父類load方法
3.最后執(zhí)行的是Person 3個(gè)Category的load方法,并且沒有順序,說明類別(Category)中的load方法要晚于類,多個(gè)類別(Category)都實(shí)現(xiàn)了load方法,這幾個(gè)load方法都會(huì)執(zhí)行,但執(zhí)行順序不確定
4.同時(shí)我們也可以看到這幾個(gè)Category load 執(zhí)行順序與其在Compile Sources中出現(xiàn)的順序一致
5.當(dāng)然多個(gè)不同的類 其load執(zhí)行順序,也與其在Compile Sources(編譯源文件)出現(xiàn)的順序一致
initialize(初始化)函數(shù)調(diào)用特點(diǎn)如下:‘
initialize在類或者其子類的第一個(gè)方法被調(diào)用前調(diào)用。即使類文件被引用進(jìn)項(xiàng)目,但是沒有使用,initialize不會(huì)被調(diào)用鞠鲜。由于是系統(tǒng)自動(dòng)調(diào)用焰情,也不需要再調(diào)用 [super initialize] 闹司,否則父類的initialize會(huì)被多次執(zhí)行嗤朴。假如這個(gè)類放到代碼中配椭,而這段代碼并沒有被執(zhí)行扼倘,這個(gè)函數(shù)是不會(huì)被執(zhí)行的色迂。
1.父類的initialize方法會(huì)比子類先執(zhí)行
2.當(dāng)子類未實(shí)現(xiàn)initialize方法時(shí),會(huì)調(diào)用父類initialize方法,子類實(shí)現(xiàn)initialize方法時(shí),會(huì)覆蓋父類initialize方法.
3.當(dāng)有多個(gè)Category都實(shí)現(xiàn)了initialize方法,會(huì)覆蓋類中的方法,只執(zhí)行一個(gè)(會(huì)執(zhí)行Compile Sources 列表中最后一個(gè)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不會(huì)被調(diào)用
我們?cè)赥eacher(繼承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方法,會(huì)調(diào)用父類initialize方法.
但為什么會(huì)打印2次呢?
我的理解:
子類不實(shí)現(xiàn)initialize方法,會(huì)把繼承父類的initialize方法并調(diào)用一遍借卧。在此之前,父類初始化時(shí),會(huì)先調(diào)用一遍自己initialize方法.所以出現(xiàn)2遍,所以為了防止父類initialize中代碼多次執(zhí)行,我們應(yīng)該這樣寫:
//In Person.m
+(void)initialize
{
if(self == [Person class])
{
? ? ? ? ? NSLog(@"%s",__FUNCTION__);
}
}
下面我們?cè)?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方法后,會(huì)覆蓋父類initialize方法,這一點(diǎn)和繼承思想一樣
我們?cè)赑erson.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)有多個(gè)Category時(shí)會(huì)怎樣了,我們?cè)赑erson,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)存在多個(gè)Category時(shí),也只執(zhí)行一個(gè),具體執(zhí)行哪一個(gè)Category中的initialize方法,測(cè)試后便可發(fā)現(xiàn),會(huì)執(zhí)行Compile Sources 列表中最后一個(gè)Category 的initialize方法
什么情況下使用:
####+load
由于調(diào)用load方法時(shí)的環(huán)境很不安全吱雏,我們應(yīng)該盡量減少load方法的邏輯敦姻。另一個(gè)原因是load方法是線程安全的,它內(nèi)部使用了鎖歧杏,所以我們應(yīng)該避免線程阻塞在load方法中
load很常見的一個(gè)使用場(chǎng)景,交換兩個(gè)方法的實(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方法主要用來對(duì)一些不方便在編譯期初始化的對(duì)象進(jìn)行賦值镰惦。比如NSMutableArray這種類型的實(shí)例化依賴于runtime的消息發(fā)送,所以顯然無法在編譯器初始化:
// In Person.m
// int類型可以在編譯期賦值
static int someNumber = 0;
static NSMutableArray *someArray;
+ (void)initialize {
? ? if (self == [Person class]) {
? ? ? ? // 不方便編譯期復(fù)制的對(duì)象在這里賦值
? ? ? ? someArray = [[NSMutableArray alloc] init];
? ? }
}
總結(jié):
load和initialize的共同點(diǎn)
1.如果父類和子類都被調(diào)用,父類的調(diào)用一定在子類之前
+load方法要點(diǎn)
當(dāng)類被引用進(jìn)項(xiàng)目的時(shí)候就會(huì)執(zhí)行l(wèi)oad函數(shù)(在main函數(shù)開始執(zhí)行之前),與這個(gè)類是否被用到無關(guān),每個(gè)類的load函數(shù)只會(huì)自動(dòng)調(diào)用一次.由于load函數(shù)是系統(tǒng)自動(dòng)加載的犬绒,因此不需要再調(diào)用[super load]旺入,否則父類的load函數(shù)會(huì)多次執(zhí)行。
1.當(dāng)父類和子類都實(shí)現(xiàn)load函數(shù)時(shí),父類的load方法執(zhí)行順序要優(yōu)先于子類
2.當(dāng)一個(gè)類未實(shí)現(xiàn)load方法時(shí),不會(huì)調(diào)用父類load方法
3.類中的load方法執(zhí)行順序要優(yōu)先于類別(Category)
4.當(dāng)有多個(gè)類別(Category)都實(shí)現(xiàn)了load方法,這幾個(gè)load方法都會(huì)執(zhí)行,但執(zhí)行順序不確定(其執(zhí)行順序與類別在Compile Sources中出現(xiàn)的順序一致)
5.當(dāng)然當(dāng)有多個(gè)不同的類的時(shí)候,每個(gè)類load 執(zhí)行順序與其在Compile Sources出現(xiàn)的順序一致
注意:
load調(diào)用時(shí)機(jī)比較早,當(dāng)load調(diào)用時(shí),其他類可能還沒加載完成,運(yùn)行環(huán)境不安全.
load方法是線程安全的懂更,它使用了鎖眨业,我們應(yīng)該避免線程阻塞在load方法.
+initialize方法要點(diǎn)
initialize在類或者其子類的第一個(gè)方法被調(diào)用前調(diào)用。即使類文件被引用進(jìn)項(xiàng)目,但是沒有使用,initialize不會(huì)被調(diào)用沮协。由于是系統(tǒng)自動(dòng)調(diào)用,也不需要顯式的調(diào)用父類的initialize卓嫂,否則父類的initialize會(huì)被多次執(zhí)行慷暂。假如這個(gè)類放到代碼中,而這段代碼并沒有被執(zhí)行,這個(gè)函數(shù)是不會(huì)被執(zhí)行的行瑞。
1.父類的initialize方法會(huì)比子類先執(zhí)行
2.當(dāng)子類不實(shí)現(xiàn)initialize方法奸腺,會(huì)把父類的實(shí)現(xiàn)繼承過來調(diào)用一遍。在此之前血久,父類的方法會(huì)被優(yōu)先調(diào)用一次
3.當(dāng)有多個(gè)Category都實(shí)現(xiàn)了initialize方法,會(huì)覆蓋類中的方法,只執(zhí)行一個(gè)(會(huì)執(zhí)行Compile Sources 列表中最后一個(gè)Category 的initialize方法)
注意:
在initialize方法收到調(diào)用時(shí),運(yùn)行環(huán)境基本健全突照。
initialize內(nèi)部也使用了鎖,所以是線程安全的氧吐。但同時(shí)要避免阻塞線程讹蘑,不要再使用鎖
load和initialize的區(qū)別:
+load 方法。注意筑舅,這里是(調(diào)用分類的 +load 方法也是如此)直接使用函數(shù)內(nèi)存地址的方式?(*load_method)(cls, SEL_load);?對(duì) +load 方法進(jìn)行調(diào)用的座慰,而不是使用發(fā)送消息?objc_msgSend?的方式。
這樣的調(diào)用方式就使得 +load 方法擁有了一個(gè)非常有趣的特性翠拣,那就是子類版仔、父類和分類中的 +load 方法的實(shí)現(xiàn)是被區(qū)別對(duì)待的。也就是說如果子類沒有實(shí)現(xiàn) +load 方法误墓,那么當(dāng)它被加載時(shí) runtime 是不會(huì)去調(diào)用父類的 +load 方法的蛮粮。同理,當(dāng)一個(gè)類和它的分類都實(shí)現(xiàn)了 +load 方法時(shí)谜慌,兩個(gè)方法都會(huì)被調(diào)用蝉揍。因此,我們常称杪Γ可以利用這個(gè)特性做一些“邪惡”的事情又沾,比如說方法混淆(Method Swizzling)。
+initialize 方法的本質(zhì))熙卡,runtime 使用了發(fā)送消息?objc_msgSend的方式對(duì) +initialize 方法進(jìn)行調(diào)用杖刷。也就是說 +initialize 方法的調(diào)用與普通方法的調(diào)用是一樣的,走的都是發(fā)送消息的流程驳癌。換言之滑燃,如果子類沒有實(shí)現(xiàn) +initialize 方法,那么繼承自父類的實(shí)現(xiàn)會(huì)被調(diào)用颓鲜;如果一個(gè)類的分類實(shí)現(xiàn)了 +initialize 方法表窘,那么就會(huì)對(duì)這個(gè)類中的實(shí)現(xiàn)造成覆蓋。因此甜滨,如果一個(gè)子類沒有實(shí)現(xiàn) +initialize 方法乐严,那么父類的實(shí)現(xiàn)是會(huì)被執(zhí)行多次的。有時(shí)候衣摩,這可能是你想要的昂验;但如果我們想確保自己的 +initialize 方法只執(zhí)行一次,避免多次執(zhí)行可能帶來的副作用時(shí),我們可以使用下面的代碼來實(shí)現(xiàn):
+ (void)initialize {
if (self == [ClassName self]) {
// ... do the initialization ...
}
}
總結(jié)
通過閱讀 runtime 的源碼既琴,我們知道了 +load 和 +initialize 方法實(shí)現(xiàn)的細(xì)節(jié)占婉,明白了它們的調(diào)用機(jī)制和各自的特點(diǎn)。下面我們繪制一張表格甫恩,以更加直觀的方式來鞏固我們對(duì)它們的理解:
? ? ? ? ? ? ? ? ? ? ? ? ? ? +load? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? +initialize
調(diào)用時(shí)機(jī)? ? ? ? ? ? ?被添加到 runtime 時(shí)? ? ? ? ? ? ? ? ? ? 收到第一條消息前逆济,可能永遠(yuǎn)不調(diào)用
調(diào)用順序? ? ? ? ? ? ? 父類->子類->分類? ? ? ? ? ? ? ? ? ? ? ?父類->子類
調(diào)用次數(shù)? ? ? ? ? ? ? 1次? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?多次
是否需要顯式調(diào)用父類實(shí)現(xiàn)? ? ? ?否? ? ? ? ? ? ? 否
是否沿用父類的實(shí)現(xiàn)? ? ? ? ? ? ? ? ?否? ? ? ? ? ? ? ?是
分類中的實(shí)現(xiàn)? ? ? ? ? ? ? ? ? ? ?類和分類都執(zhí)行? ? ? ? ? ? 覆蓋類中的方法,只執(zhí)行分類的實(shí)現(xiàn)
值得注意的是(*load_method)(cls, SEL_load)磺箕,load方法是直接使用函數(shù)指針調(diào)用奖慌,也就是走C語(yǔ)言函數(shù)調(diào)用的流程,不是發(fā)送消息滞磺,并不會(huì)走消息轉(zhuǎn)發(fā)的流程升薯,也就是說,如果一個(gè)類實(shí)現(xiàn)了load函數(shù)就會(huì)調(diào)用击困,如果沒有實(shí)現(xiàn)也不會(huì)調(diào)用該類的父類load函數(shù)實(shí)現(xiàn)涎劈,如果父類實(shí)現(xiàn)了load函數(shù)的話。category調(diào)用load方法也是一樣的道理阅茶。
就是發(fā)送消息蛛枚,這是和load函數(shù)實(shí)現(xiàn)不一樣的地方,load函數(shù)的調(diào)用直接是函數(shù)指針的調(diào)用脸哀,而initialize函數(shù)是消息的轉(zhuǎn)發(fā)蹦浦。所以,class的子類就算沒有實(shí)現(xiàn)initialize函數(shù)撞蜂,也會(huì)調(diào)用父類的initialize函數(shù)盲镶,如果子類實(shí)現(xiàn)了initialize函數(shù),則子類不會(huì)調(diào)用父類的initialize函數(shù)蝌诡。
總結(jié)
通過分別對(duì)load和initialize源代碼的實(shí)現(xiàn)細(xì)節(jié)溉贿,我們知道了它們各自的特點(diǎn),總的如下:
1.load在被添加到runtime的時(shí)候加載浦旱,initialize是類第一次收到消息的時(shí)候被加載宇色,load是在main函數(shù)之前,initialize是在main函數(shù)之后颁湖。
2.load方法的調(diào)用順序是:superClass -> class -> category宣蠕;initialize方法的調(diào)用順序是:superClass -> class。都不需要顯示調(diào)用父類的方法甥捺,系統(tǒng)會(huì)自動(dòng)調(diào)用抢蚀,load方法是函數(shù)指針調(diào)用,initialize是發(fā)送消息涎永。子類如果沒有實(shí)現(xiàn)load函數(shù)思币,子類是不會(huì)調(diào)用父類的load函數(shù)的鹿响,但是子類沒有實(shí)現(xiàn)initialize函數(shù)羡微,則會(huì)調(diào)用父類的initialize函數(shù)谷饿。
3.load和initialize內(nèi)部實(shí)現(xiàn)都加了線程鎖,是線程安全的妈倔,因此博投,這兩個(gè)函數(shù)應(yīng)該做一些簡(jiǎn)單的工作,不適合復(fù)雜的工作盯蝴。
4.load函數(shù)通常用來進(jìn)行Method Swizzle毅哗,initialize函數(shù)則通常初始化一些全局變量,靜態(tài)變量捧挺。