[toc]
參考
code
// NSObject 有實現(xiàn)該方法
+ (void)initialize;
objc4源碼解析
思路
既然 +initialize
方法會在類第一次接收到消息時被調(diào)用,
所以 objc_msgSend()
方法內(nèi)部必然有調(diào)用 +initialize
榨呆。
但是 objc_msgSend()
的C源碼沒有開源, 只提供了匯編代碼水由。
我們只能通過其尋找方法的鏈路思考:
isa -> 類對象/元類對象, 查找方法, 調(diào)用; 若找不到, 查找父類 ↓
super_class -> 類對象/元類對象, 查找方法, 調(diào)用; 若找不到, 繼續(xù)查找父類 ↓
...
猜想, 在查找方法這一步, 有可能調(diào)用了 +initialize
亥鬓。
源碼(部分)
// 獲取類方法
Method class_getClassMethod(Class cls, SEL sel) {
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
Method class_getInstanceMethod(Class cls, SEL sel) {
if (!cls || !sel) return nil;
lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER); // 查找IMP
return _class_getMethod(cls, sel);
}
// 查找方法
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) {
// 未被初始化, 就調(diào)用
if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock); //
}
// ...
}
static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock) {
return initializeAndMaybeRelock(cls, obj, lock, true);
}
static Class initializeAndMaybeRelock(Class cls, id inst, mutex_t& lock, bool leaveLocked) {
// ...
Class nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
initializeNonMetaClass(nonmeta);
}
void initializeNonMetaClass(Class cls) {
supercls = cls->superclass;
// 有父類, 且父類未初始化, 遞歸調(diào)用, 先初始化父類
if (supercls && !supercls->isInitialized()) {
initializeNonMetaClass(supercls);
}
callInitialize(cls);
// ...
}
// objc-initialize.mm
void callInitialize(Class cls) {
// 調(diào)用了 initialize
((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
asm("");
}
結(jié)論
調(diào)用方式
由系統(tǒng)自動調(diào)用, 不能手動調(diào)用
系統(tǒng)通過消息發(fā)送機制, objc_msgSend()
調(diào)用順序
先調(diào)用父類的 +initialize, 再調(diào)用子類的 +initialize
(如果子沒實現(xiàn)就調(diào)用父類的, 根類NSObject有實現(xiàn), 保證了不會報錯)
父類 / 分類
+initialize
是通過 objc_msgSend()
進(jìn)行調(diào)用的, 所以有以下特點:
-
initialize 遵循繼承規(guī)則, 父類的 initialize 會比子類先執(zhí)行;
如果被發(fā)消息的類及其分類 都沒有實現(xiàn) initialize, 則會查找并調(diào)用其父類的 initialize, 這種情況父類的initialize會被調(diào)用多次歌逢。
分類覆蓋主類, 如果被發(fā)消息的類的分類實現(xiàn)了+initialize 方法, 就會覆蓋這個類中的實現(xiàn)洗搂。
調(diào)用時機
在main()函數(shù)之后, 在該類或其子類收到第一條消息之前;
當(dāng)向該類發(fā)送的第一個消息, 一般是類消息首先調(diào)用, 常見的是alloc;
《Apple Document》
Initializes the class before it receives its first message.
The runtime sends initialize to each class in a program just before the class, or any class that inherits from it, is sent its first message from within the program. Superclasses receive this message before their subclasses.
調(diào)用次數(shù)
結(jié)論:
不一定只被調(diào)用一次;
若子類及其分類未實現(xiàn) initialize, 父類的 initialize 會被調(diào)用多次;
但父類的initialize被調(diào)用多次, 并不代表父類被初始化多次, 僅僅是子類調(diào)用了父類的方法而已。
分析:
initialize 的調(diào)用與普通方法一樣都是使用 runtime 的消息發(fā)送機制腺阳。
-
若第一次給某個子類發(fā)送消息, 初始化該子類前需先初始化其父類, 此時父類的initialize被調(diào)用1次; (若此前父類已被初始化, 則不會再調(diào)用, 但也已經(jīng)被調(diào)用一次)
消息機制會通過子類的isa找到子類的元類, 查找該元類的方法列表, 如果子類沒有實現(xiàn)initialize, 會通過元類的super_class指針查找父元類, 然后查找并調(diào)用父元類的initialize, 導(dǎo)致父元類的initialize被調(diào)用兩次; (類方法存儲在元類的方法列表)
而如果子類沒有實現(xiàn)initialize, 會查找并調(diào)用父類的initialize, 導(dǎo)致父類的initialize被調(diào)用兩次;
所以通常要在方法內(nèi)加一個判斷, 確認(rèn)當(dāng)前要初始化的是不是本類, 防止因子類的調(diào)用而將原本只需執(zhí)行一次的代碼執(zhí)行兩次缘圈。
- 另外, 初始化子類前, 系統(tǒng)會自動初始化父類, 子類不要手動調(diào)用super, 否則父類的initialize會被調(diào)用多次。
《Apple Document》
The superclass implementation may be called multiple times if subclasses do not implement initialize—the runtime will call the inherited implementation—or if subclasses explicitly call [super initialize]. If you want to protect yourself from being run multiple times, you can structure your implementation along these lines: Listing 1
+ (void)initialize {
if (self == [ClassName self]) {
// ... do the initialization ...
}
}
調(diào)用必然性
不一定被調(diào)用;
initialize 是懶加載的, 如果程序一直沒有給某個類或它的子類發(fā)送消息, 那么它永遠(yuǎn)不會被調(diào)用, 這一點有利于節(jié)省系統(tǒng)資源, 避免浪費野崇。
應(yīng)用場景
蘋果提供 initialize方法, 就是給開發(fā)者使用的, 第一次使用這個類的時候做一些事情称开。
initialize方法一般用于初始化全局變量 或 靜態(tài)變量。
無法在編譯期設(shè)定的全局變量, 可以放在initialize方法中初始化乓梨。
安全性
線程安全 線程阻塞
運行期系統(tǒng)會確保initialize方法是在線程安全的環(huán)境中執(zhí)行, 即只有執(zhí)行initialize的那個線程可以操作類或類實例鳖轰。其他線程都要先阻塞, 等待initialize執(zhí)行完
《Apple Document》
The runtime sends the initialize message to classes in a thread-safe manner. That is, initialize is run by the first thread to send a message to a class, and any other thread that tries to send a message to that class will block until initialize completes.
Because initialize is called in a blocking manner, it’s important to limit method implementations to the minimum amount of work necessary possible. Specifically, any code that takes locks that might be required by other classes in their initialize methods is liable to lead to deadlocks. Therefore, you should not rely on initialize for complex initialization, and should instead limit it to straightforward, class local initialization.
面試題
和 init 的區(qū)別?
+ (void)initialize; // 類方法, 每個類第一次被初始化的時候調(diào)用一次; (alloc)
- (instancetype)init; // 對象方法, 每個對象初始化的時候調(diào)用一次;
initialize 的調(diào)用在 init 之前。
和 load 的區(qū)別
見【load - 面試題】
什么情況下initialize會被調(diào)用多次?
【見本文 - 調(diào)用次數(shù)】
initialize 初始化類指的是創(chuàng)建類對象?
應(yīng)該是 [TBC]