概述
Objective-C作為一門面向?qū)ο笳Z言,有類和對象的概念。編譯后,類相關(guān)的數(shù)據(jù)結(jié)構(gòu)會(huì)保留在目標(biāo)文件中,在運(yùn)行時(shí)得到解析和使用。在應(yīng)用程序運(yùn)行起來的時(shí)候,類的信息會(huì)有加載和初始化過程。
就像Application有生命周期回調(diào)方法一樣,在Objective-C的類被加載和初始化的時(shí)候,也可以收到方法回調(diào),可以在適當(dāng)?shù)那闆r下做一些定制處理。而這正是load和initialize方法可以幫我們做到的。
+ (void)load;
+ (void)initialize;
可以看到這兩個(gè)方法都是以“+”開頭的類方法,返回為空。通常情況下,我們在開發(fā)過程中可能不必關(guān)注這兩個(gè)方法。如果有需要定制,我們可以在自定義的NSObject子類中給出這兩個(gè)方法的實(shí)現(xiàn),這樣在類的加載和初始化過程中,自定義的方法可以得到調(diào)用。
+load
顧名思義,+load方法在這個(gè)文件被程序裝載時(shí)調(diào)用。只要是在Compile Sources中出現(xiàn)的文件總是會(huì)被裝載,這與這個(gè)類是否被用到無關(guān),因此+load方法總是在main函數(shù)之前調(diào)用。
調(diào)用方式:
會(huì)循環(huán)調(diào)用所有類的 +load 方法。注意,這里是(調(diào)用分類的 +load 方法也是如此)直接使用函數(shù)內(nèi)存地址的方式 (*load_method)(cls, SEL_load); 對 +load 方法進(jìn)行調(diào)用的,而不是使用發(fā)送消息 objc_msgSend 的方式。
這樣的調(diào)用方式就使得 +load 方法擁有了一個(gè)非常有趣的特性,那就是子類、父類和分類中的 +load 方法的實(shí)現(xiàn)是被區(qū)別對待的。也就是說如果子類沒有實(shí)現(xiàn) +load 方法,那么當(dāng)它被加載時(shí) runtime 是不會(huì)去調(diào)用父類的 +load 方法的。同理,當(dāng)一個(gè)類和它的分類都實(shí)現(xiàn)了 +load 方法時(shí),兩個(gè)方法都會(huì)被調(diào)用。
要點(diǎn):
- 調(diào)用時(shí)機(jī)比較早,運(yùn)行環(huán)境有不確定因素。具體說來,在iOS上通常就是App啟動(dòng)時(shí)進(jìn)行加載,但當(dāng)load調(diào)用的時(shí)候,并不能保證所有類都加載完成且可用,必要時(shí)還要自己負(fù)責(zé)做auto release處理。
補(bǔ)充上面一點(diǎn),對于有依賴關(guān)系的兩個(gè)庫中,被依賴的類的+load會(huì)優(yōu)先調(diào)用。但在一個(gè)庫之內(nèi),父、子類、類別之間調(diào)用有順序,不同類之間調(diào)用順序是不確定的。 - 關(guān)于繼承:對于一個(gè)類而言,沒有+load方法實(shí)現(xiàn)就不會(huì)調(diào)用,不會(huì)考慮對NSObject的繼承,就是不會(huì)沿用父類的+load。
- 父類和本類的調(diào)用:父類的方法優(yōu)先于子類的方法。一個(gè)類的+load方法不用寫明[super load],父類就會(huì)收到調(diào)用。
- 本類和Category的調(diào)用:本類的方法優(yōu)先于類別(Category)中的方法。Category的+load也會(huì)收到調(diào)用,但順序上在本類的+load調(diào)用之后。
- 不會(huì)直接觸發(fā)initialize的調(diào)用。
+initialize
+initialize 方法是在類或它的子類收到第一條消息之前被調(diào)用的,這里所指的消息包括實(shí)例方法和類方法的調(diào)用,并且只會(huì)調(diào)用一次。initialize方法實(shí)際上是一種惰性調(diào)用,也就是說如果一個(gè)類一直沒被用到,那它的initialize方法也不會(huì)被調(diào)用,這一點(diǎn)有利于節(jié)約資源。
調(diào)用方式:
runtime 使用了發(fā)送消息 objc_msgSend 的方式對 +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ì)對這個(gè)類中的實(shí)現(xiàn)造成覆蓋。
要點(diǎn):
- initialize的自然調(diào)用是在第一次主動(dòng)使用當(dāng)前類的時(shí)候。
- 在initialize方法收到調(diào)用時(shí),運(yùn)行環(huán)境基本健全。
- 關(guān)于繼承:和load不同,即使子類不實(shí)現(xiàn)initialize方法,會(huì)把父類的實(shí)現(xiàn)繼承過來調(diào)用一遍,就是會(huì)沿用父類的+initialize。(沿用父類的方法中,self還是指子類)
- 父類和本類的調(diào)用:子類的+initialize將要調(diào)用時(shí)會(huì)激發(fā)父類調(diào)用的+initialize方法,所以也不需要在子類寫明[super initialize]。(本著除主動(dòng)調(diào)用外,只會(huì)調(diào)用一次的原則,如果父類的+initialize方法調(diào)用過了,則不會(huì)再調(diào)用)
- 本類和Category的調(diào)用:Category中的+initialize方法會(huì)覆蓋本類的方法,只執(zhí)行一個(gè)Category的+initialize方法。
類別(Category)
對于+initialize,只有最后一個(gè)類別執(zhí)行,本類的+initialize和前面類別的+initialize被隱藏。
而對于+load,本類和本類的所有類別都執(zhí)行,并且如果Apple的文檔中介紹順序一樣:先執(zhí)行類自身的實(shí)現(xiàn),再執(zhí)行類別中的實(shí)現(xiàn)。
擴(kuò)展
因?yàn)閮蓚€(gè)方法只會(huì)被系統(tǒng)調(diào)用一次(除主動(dòng)調(diào)用外),并且是線程安全的,可以用來作為單例的實(shí)現(xiàn)。(可以用+initialize,+load有些隱患,看這里)
?注意
- 在使用時(shí)都不要過重地依賴于這兩個(gè)方法,除非真正必要。
- 謹(jǐn)慎在分類中實(shí)現(xiàn)+initialize方法,因?yàn)槿绻诜诸愔袑?shí)現(xiàn)了,本類實(shí)現(xiàn)的+initialize方法將不會(huì)被調(diào)用。
- 謹(jǐn)慎在分類中實(shí)現(xiàn)+load方法。因?yàn)槿绻诒绢愔袑?shí)現(xiàn)+load方法混淆A、B兩個(gè)方法,分類中也混淆A、B,因?yàn)楸绢惡头诸惖?load都實(shí)現(xiàn)了,所以都會(huì)調(diào)用,A、B在本類中置換后,又在分類中置換了回來。
- load方法通常用來進(jìn)行Method Swizzle,initialize方法一般用于初始化全局變量或靜態(tài)變量。
- load和initialize方法內(nèi)部使用了鎖,因此它們是線程安全的。實(shí)現(xiàn)時(shí)要盡可能保持簡單,避免阻塞線程,不要再使用鎖。
問題
問題:
- 子類、父類、分類中的相應(yīng)方法什么時(shí)候會(huì)被調(diào)用?
- 需不需要在子類的實(shí)現(xiàn)中顯式地調(diào)用父類的實(shí)現(xiàn)?
解答:
- super的方法會(huì)成功調(diào)用,但是這是多余的,因?yàn)閞untime會(huì)自動(dòng)對父類的+load方法進(jìn)行調(diào)用,而+initialize則會(huì)隨子類自動(dòng)激發(fā)父類的方法(如Apple文檔中所言)不需要顯示調(diào)用。另一方面,如果父類中的方法用到的self(像示例中的方法),其指代的依然是類自身,而不是父類。
總結(jié)
+load | +initialize | |
---|---|---|
調(diào)用時(shí)機(jī) | 被添加到 runtime 時(shí) | 到第一條消息前,可能永遠(yuǎn)不調(diào)用 |
同一個(gè)類,調(diào)用次數(shù) (不考慮主動(dòng)調(diào)用) |
1次 | 1次 |
調(diào)用順序 | 父類->本類->分類 | 父類->本類(如果有分類,則調(diào)用分類) |
若自身未實(shí)現(xiàn),是否沿用父類的方法? | 否 | 是 |
類別中的定義 | 全都執(zhí)行,但后于本類的方法 | 覆蓋本類的方法,只執(zhí)行一個(gè) |
線程安全 | 安全 | 安全 |
結(jié)束語
參考
-
Objective-C +load vs +initialize
文章根據(jù)runtime源碼來解釋+load和+initialize特性。