1. 程式人生 > >Objc類的載入和初始化(+load和+initialize方法)

Objc類的載入和初始化(+load和+initialize方法)

作為一個程式設計師,絕大多數時候你都不需要關心一個類是怎麼被載入進記憶體的。這裡面 runtime linker 在你的程式碼還沒跑起來之前就已經做了很多複雜的工作。

對於大多類來說,知道這一點就已經相當足夠了。但是,有一些類可能需要做一些特殊的準備工作。比如初始化一個全域性的表,從 UserDefaults 裡面讀取配置並快取起來,又或者做一些其他的準備工作。

ObjC 提供了兩種方法來實現這些事情:

1 2 + initialize  + load

+load

如果你的類實現了 +load 方法,這個方法就會在類被載入的時候呼叫。這個呼叫時機是很早的。如果你是在被其他應用引用的應用(Application)或框架(Framework)裡實現了這個方法,它甚至會比 main() 函式還早被觸發。如果你是在一個可以被載入的 bundle 裡面實現這個方法,那當 bundle 被載入的時候這個方法就會被呼叫。

因為 +load 方法過早被呼叫,所以應用起來會有點困難。很多時候有些類是要比別人更早被載入的,這樣你無法判斷別人是不是早就被呼叫過 +load 方法了。更糟糕的時候,你的應用中包含的 C++ 靜態初始化函式在這個時機點是還沒被呼叫的,如果你在 +load 裡面呼叫了相關的程式碼,就很有可能會 crash。好訊息是你連結的 frameworks 是保證在 +load 呼叫前就載入過了的,所以在這裡使用 framewroks 是安全的。還有父類也是保證完全載入過了,所以使用父類也是沒問題的。

+load 這個方法有一個有意思的特性,就是 runtime 會把所有 category 裡面實現了 +load 的方法全部調一遍。也就是說如果你在多個 category 裡面都實現了 +load 方法,這些方法都會被呼叫一次。這種設計可能跟你認識到 category 的機制完全相反,不過你要知道 +load 方法不是一個普通的方法。這個特性決定了 +load 是一個幹壞事的絕佳場所,比如 swizzling。

+initialize

相比而言,+initialize 方法就要正常的多了,通常也是一個更好的安置程式碼的地方。+initialize 有意思的地方在於它會很晚才被呼叫,甚至它有可能完全不會被呼叫。當一個類被載入的時候,+initialize 不會被呼叫,當一個訊息傳送給這個類的時候(譯者注:ObjC 的方法呼叫都是通過 runtime 的訊息機制,objc_sendMsg 方法),runtime 就會檢查這個方法有沒有被呼叫過,如果沒有就呼叫之。大概可以認為是這樣的:

1 2 3 4 5 6 id objc_msgSend(id self, SEL _cmd, …) { if
(!self->class->initialized) [self->class initialize]; …send the message… }

當然真正的實現不會這麼簡單,還要解決執行緒安全之類的問題,不過大概就是這麼個意思吧。每個類知會呼叫一次 +initialize 方法,而且只會在這個類收到第一個訊息的時候被呼叫。跟 +load 方法一樣,+initialize 會先呼叫這個類所有的父類,最後才調到自己的 +initialize 方法。

這就使得 +initialize 用起來要比 +load 方法更安全,因為呼叫時機的環境要安全得多。當然這時候的環境還要取決於第一條訊息傳送的結果,不過可以保證呼叫的時機一定比 NSApplicationMain() 要晚。

由於 +initialize 是 lazily run 的,所以這裡就不是做註冊事件的好地方。比如說,NSValueTransformer 和 NSURLProtocol 就不能用 +initialize 來註冊自己,因為這就成了一個先有雞還是先有蛋的問題。

這個方法適合用來做需要在類被載入後做的事情。由於這個方法執行的時候環境容錯性更好,所以你可以使用的方法也就比 +load 自由得多,也因為這個方法是 lazy 呼叫的,所以你在這個方法中使用的資源就不會事先申請而造成浪費。

+initialize 的使用還有個小伎倆,我上面的虛擬碼裡提到 runtime 會呼叫:

1 [self->class initialize]

這就導致 ObjC 做 selector 實現的檢查,如果當前類沒有實現這個方法,那麼父類的方法就會被呼叫。不只在虛擬碼裡,實際上也是這樣的。所以,你的 +initialize 就得寫成下面這樣:

1 2 3 4 5 6 7 + (void)initialize { if(self == [WhateverClass class]) { …perform initialization… } }

如果沒有做這個檢查,如果你有沒實現 +initialize 的子類,你的程式碼就會被呼叫兩次。就算你沒有任何子類,Apple 的 KVO 也會動態建立沒有實現 +initialize 的子類。

結論

ObjC 提供了兩種自動執行類初始化程式碼的方法。+load 方法保證了會在 class 被載入的時候呼叫,這個時機很早,所以對於需要很早被執行的程式碼來說是很有用的。但是在這個時機跑的程式碼也可以是很危險的,畢竟這個時候的環境比較惡劣。

由於 +initialize 方法是 lazy 觸發的,所以對於初始化設定的環境就要友好得多。只要不是在類接收第一條訊息之前一定要做的事情,都可以在這個方法裡面做。