一、程式碼準備
- 定義
Person
類, 繼承自NSObject
, 並實現+(void)initialize
方法
- 定義
Person
類的CategoryPerson+Test1
, 並實現+(void)initialize
方法
- 定義
Person
類的CategoryPerson+Test2
, 並實現+(void)initialize
方法
- 定義
Student
類, 繼承自Person
, 並實現+(void)initialize
方法
- 定義
Student
類的CategoryStudent+Test1
, 並實現+(void)initialize
方法
- 定義
Student
類的CategoryStudent+Test2
, 並實現+(void)initialize
方法
二、執行程式
1、不主動呼叫任何程式碼, 執行程式
main.m
中不新增任何程式碼, 直接執行程式
- 根據結果, 可以知道任何的
+(void)initialize
方法沒有被呼叫
2、Person
類呼叫alloc
方法
main.m
中呼叫[Person alloc]
, 底層相當於
objc_msgSend([Person class], @selector(alloc));
複製程式碼
`
- 可以發現,
Person+Test1
的程式碼被呼叫了 - 檢視一下檔案的編譯順序, 可以發現
Person+Test1
比Person+Test2
編譯的晚
- 這說明在
Person
類通過訊息機制呼叫方法時, 會通過訊息機制呼叫+(void)initialize
方法
objc_msgSend([Person class], @selector(initialize))
複製程式碼
3、Student
類呼叫alloc
方法
main.m
中呼叫[Student alloc]
, 底層相當於
objc_msgSend([Student class], @selector(alloc));
複製程式碼
- 可以看到
[Student alloc]
呼叫時, 會先呼叫Person
類的+(void)initialize
方法, 在呼叫Student
類的+(void)initialize
方法
推論: 當一個類在第一次接受訊息時, 會呼叫他自己的
+(void)initialize
方法, 如果他有父類, 那麼就會優先呼叫父類的+(void)initialize
方法
三、檢視關於類呼叫+(void)initialize
方法的原始碼
- 在原始碼裡搜尋
Method class_getInstanceMethod(Class cls, SEL sel)
函式
- 找到程式碼
lookUpImpOrNil(cls, sel, nil, NO, NO, YES);
- 進入
IMP lookUpImpOrNil(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver)
函式後, 找到IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
- 進入
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver)
函式中
- 可以看到有判斷, 如果需要初始化, 並且類沒有初始化, 那麼呼叫
_class_initialize (_class_getNonMetaClass(cls, inst));
- 進入
void _class_initialize(Class cls)
函式中, 我們可以看到, 如果被傳入的類有父類, 並且父類沒有初始化, 會通過遞迴將父類傳入
- 向下可以找到程式碼
callInitialize(cls);
, 即: 呼叫初始化方法, 並傳入類物件(如果有父類, 父類沒有初始化的情況下, 會先初始化父類)
- 進入
void callInitialize(Class cls)
函式, 可以看到, 底層是通過訊息機制, 呼叫了類物件的initialize
方法
總結:
通過原始碼可以看到, 當一個類在查詢方法的時候, 會先判斷當前類是否初始化, 如果沒有初始化就會去掉用initialize
方法
如果這個類的父類沒有初始化, 就會先呼叫父類的initialize
方法, 再呼叫自己的initialize
方法
類在呼叫initialize
時, 使用的是objc_msgSend
訊息機制呼叫
- 所以, 在呼叫
[Person alloc]
方法時, 會使用訊息機制
呼叫Person
類的initialize
方法, 又因為Person
存在兩個CategoryPerson+Test1
和Person+Test2
, 此時就看哪一個Category
最後一個編譯, 就會呼叫裡面的initialize
方法 - 在呼叫
[Student alloc]
時, 會呼叫Student
類的initialize
方法, 但是因為此時父類Person
還沒有初始化, 所以會先呼叫Person
的initialize
方法
四、移除子類的+(void)initialize
方法, 再次給子類傳送訊息
- 刪除
Student
、Student+Test1
和Student+Test2
中的initialize
方法
- 可以看到,
Person (Test1) - initialize
列印了兩次, 說明Person
的initialize
方法被呼叫了兩次 - 前面已經說過, 當
Student
類在第一次接收訊息時, 會進行初始化, 如果父類沒有初始化, 會先給父類初始化 - 所以, 第一次的
Person (Test1) - initialize
列印, 實際就是Person
類初始化時呼叫的 - 而一個類只能初始化一次, 所以第二次的列印, 實際是
Student
類初始化時呼叫的 Student
初始化呼叫initialize
方法時, 用的下面方式
objc_msgSend([Student class], @selector(initialize))
複製程式碼
- 在OC中, 使用訊息機制呼叫類方法時, 呼叫順序如下:
類物件
通過isa
找到元類物件
, 在元類物件
的方法列表中查詢方法, 如果有就會呼叫- 如果
元類物件
中沒有呼叫的方法, 就會通過元類物件
的superclass
找到父類
的元類物件
, 接著父類
的元類物件
的方法列表中查詢方法, 如果有就會呼叫
- 所以,
Student
實際上在初始化時, 呼叫的是objc_msgSend([Student class], @selector(initialize))
, 但是因為Student
並沒有實現initialize
方法, 所以Student
呼叫了父類Person
的initialize
方法
五、面試題
1、load
和initialize
方法的區別是什麼?
- 呼叫方式
load
是根據函式地址直接呼叫initialize
是通過objc_msgSend
呼叫
- 呼叫時刻
load
是runtime
載入類、分類的時候呼叫(只會呼叫一次)initialize
是類第一次接收到訊息的時候呼叫, 每一個類只會initialize
一次(如果子類沒有實現initialize
方法, 會呼叫父類的initialize
方法, 所以父類的initialize
方法可能會呼叫多次)
2、load和initializee的呼叫順序
- load
- 先呼叫類的
load
, 在呼叫分類的load
- 先編譯的類, 優先呼叫
load
, 呼叫子類的load
之前, 會先呼叫父類的load
- 先編譯的分類, 優先呼叫
load
- 先呼叫類的
- initialize
- 先初始化分類, 後初始化子類
- 通過訊息機制呼叫, 當子類沒有
initialize
方法時, 會呼叫父類的initialize
方法, 所以父類的initialize
方法會呼叫多次