為什麼方法交換的程式碼總是放在 +load 裡?不能放進看起來很相似的 +initialize 嗎?
+load 和 +initialize 看起來都像是「類別準備好時呼叫一次的方法」,但從呼叫時機、呼叫方式到繼承規則,全部都不同。這也是 Objective-C 面試常見主題;如果理解錯誤,很容易困惑「為什麼這段程式碼會執行兩次?」值得整理一次。
快速比較表
+load |
+initialize |
|
|---|---|---|
| 呼叫時機 | 類別在執行階段載入時(main 之前) | 類別在收到第一個訊息前(lazy) |
| 呼叫方式 | 直接呼叫函式指標 | 經由 objc_msgSend |
| Category | 類別與 category 的實作都會各自呼叫 | category 實作會覆寫類別實作 |
| 繼承 | 只會呼叫自行實作的類別 | 會被繼承 — 可能因為子類別而呼叫多次 |
| 如果未使用? | 仍然會呼叫 | 不使用該類別就永遠不會呼叫 |
接著逐一拆解只看表格不容易掌握的部分。
+load:早於 main,無條件執行
+load會在包含該類別(或 category)的二進位檔由執行階段載入時呼叫,也就是main 函式開始執行之前。即使 App 完全沒有使用該類別,也會被呼叫。
它的呼叫方式很特殊。不經過 objc_msgSend,而是直接透過函式指標呼叫。因此不適用一般的覆寫規則。
- 子類別沒有實作
+load時,不會改由父類別實作代為呼叫。 - 類別的
+load和 category 的+load會各自呼叫,這不是覆寫。 - 順序有保證:父類別的
+load先於子類別,類別的+load先於 category。
這些特性正好與方法交換相互配合。即使在 category 中實作 +load,也不會干擾原始類別的 +load,而且能在 App 生命週期最早的時機確實執行一次。
但代價也很明顯。+load會**直接計入 App 啟動時間。**所有類別的+load都會在 main 前依序執行,因此在這裡做繁重工作會延遲第一個畫面。這就是 Apple 多年來建議「盡量避免 +load」的原因。實際上,在+load中,self 所屬映像檔以外的其他類別可能尚未載入,能做的事情也會受到限制。
+initialize:第一個訊息前才延遲執行
+initialize採取完全相反的策略。執行階段會在類別收到第一個訊息前呼叫它。如果 App 從未使用該類別,就永遠不會呼叫。這是一個不增加啟動負擔的 lazy 初始化時機。
這裡會經過 objc_msgSend,因此會像一般方法一樣套用**繼承規則。**著名的陷阱正是在這裡出現。
@implementation Animal
+ (void)initialize {
NSLog(@"initialize: %@", self);
}
@end
@interface Dog : Animal
@end
@implementation Dog
@end
對 Dog 傳送第一個訊息後,日誌會如下輸出。
initialize: Animal
initialize: Dog
Animal的+initialize會執行兩次。一次是 Animal 自己的部分,另一次是未實作+initialize的 Dog 繼承父類別實作後執行。因此,+initialize的慣例實作會加入類別檢查。
+ (void)initialize {
if (self == [Animal class]) {
// 只初始化真正 Animal 屬於自己的部分
}
}
補充一下,+initialize本身具備執行緒安全性,因為執行階段會以類別為單位加鎖後呼叫。不需要再疊加dispatch_once。
實務上的選擇標準
判斷標準很簡單。
- 方法交換、類別註冊等「無條件、越早越好」的工作 →
+load(但要保持最小化) - 只有使用該類別時才需要的準備工作 →
+initialize(必須檢查 self) - 大多數初始化 → 其實兩者都不是,
dispatch_once使用 singleton 或 lazy 屬性就足夠
Swift 根本沒有這個煩惱。Swift 完全不提供相當於+load的機制,也沒有官方管道能在 main 前插入全域執行程式碼。相反地,型別屬性(static let)在語言層級保證 lazy 與執行緒安全,取代+initialize的角色。就 App 啟動效能而言,這是 Swift 在架構上有利的地方之一。
總結
+load會在 main 前無條件地透過函式指標直接呼叫 — 類別與 category 的實作都會各自執行+initialize會在第一個訊息前以 lazy 方式經由 msgSend 呼叫 — 未使用的類別永遠不會執行+initialize可能因繼承而執行多次,因此慣例上會進行if (self == [MyClass class])檢查- 方法交換放在
+load的原因:最早時機 + 不依賴 category 的呼叫 + 確實執行一次 - 濫用
+load會直接拖慢 App 啟動時間 — 繁重初始化應延後到 lazy 執行
搭配執行階段系列的其他文章(objc_msgSend、訊息轉送、方法交換、KVO)一起看,就會發現+load與+initialize的差異最終都分歧於同一個軸:是否經過 msgSend。理解呼叫路徑後,就不必死背規則。

