小碼哥iOS學習筆記第六天: initialize方法

冰凌天發表於2019-03-04

一、程式碼準備

  • 定義Person類, 繼承自NSObject, 並實現+(void)initialize方法
小碼哥iOS學習筆記第六天: initialize方法
  • 定義Person類的CategoryPerson+Test1, 並實現+(void)initialize方法
小碼哥iOS學習筆記第六天: initialize方法
  • 定義Person類的CategoryPerson+Test2, 並實現+(void)initialize方法
小碼哥iOS學習筆記第六天: initialize方法
  • 定義Student類, 繼承自Person, 並實現+(void)initialize方法
小碼哥iOS學習筆記第六天: initialize方法
  • 定義Student類的CategoryStudent+Test1, 並實現+(void)initialize方法
小碼哥iOS學習筆記第六天: initialize方法
  • 定義Student類的CategoryStudent+Test2, 並實現+(void)initialize方法
小碼哥iOS學習筆記第六天: initialize方法

二、執行程式

1、不主動呼叫任何程式碼, 執行程式

  • main.m中不新增任何程式碼, 直接執行程式
小碼哥iOS學習筆記第六天: initialize方法
  • 根據結果, 可以知道任何的+(void)initialize方法沒有被呼叫

2、Person類呼叫alloc方法

  • main.m中呼叫[Person alloc], 底層相當於
objc_msgSend([Person class], @selector(alloc));
複製程式碼
小碼哥iOS學習筆記第六天: initialize方法

`

  • 可以發現, Person+Test1的程式碼被呼叫了
  • 檢視一下檔案的編譯順序, 可以發現Person+Test1Person+Test2編譯的晚
小碼哥iOS學習筆記第六天: initialize方法
  • 這說明在Person類通過訊息機制呼叫方法時, 會通過訊息機制呼叫+(void)initialize方法
objc_msgSend([Person class], @selector(initialize))
複製程式碼

3、Student類呼叫alloc方法

  • main.m中呼叫[Student alloc], 底層相當於
objc_msgSend([Student class], @selector(alloc));
複製程式碼
小碼哥iOS學習筆記第六天: initialize方法
  • 可以看到[Student alloc]呼叫時, 會先呼叫Person類的+(void)initialize方法, 在呼叫Student類的+(void)initialize方法

推論: 當一個類在第一次接受訊息時, 會呼叫他自己的+(void)initialize方法, 如果他有父類, 那麼就會優先呼叫父類的+(void)initialize方法

三、檢視關於類呼叫+(void)initialize方法的原始碼

  • 在原始碼裡搜尋Method class_getInstanceMethod(Class cls, SEL sel)函式
小碼哥iOS學習筆記第六天: initialize方法
  • 找到程式碼lookUpImpOrNil(cls, sel, nil, NO, NO, YES);
小碼哥iOS學習筆記第六天: initialize方法
  • 進入IMP lookUpImpOrNil(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver)函式後, 找到IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
小碼哥iOS學習筆記第六天: initialize方法
  • 進入IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver)函式中
小碼哥iOS學習筆記第六天: initialize方法
  • 可以看到有判斷, 如果需要初始化, 並且類沒有初始化, 那麼呼叫_class_initialize (_class_getNonMetaClass(cls, inst));
小碼哥iOS學習筆記第六天: initialize方法
  • 進入void _class_initialize(Class cls)函式中, 我們可以看到, 如果被傳入的類有父類, 並且父類沒有初始化, 會通過遞迴將父類傳入
小碼哥iOS學習筆記第六天: initialize方法
  • 向下可以找到程式碼callInitialize(cls);, 即: 呼叫初始化方法, 並傳入類物件(如果有父類, 父類沒有初始化的情況下, 會先初始化父類)
小碼哥iOS學習筆記第六天: initialize方法
  • 進入void callInitialize(Class cls)函式, 可以看到, 底層是通過訊息機制, 呼叫了類物件的initialize方法
小碼哥iOS學習筆記第六天: initialize方法

總結:
通過原始碼可以看到, 當一個類在查詢方法的時候, 會先判斷當前類是否初始化, 如果沒有初始化就會去掉用initialize方法
如果這個類的父類沒有初始化, 就會先呼叫父類的initialize方法, 再呼叫自己的initialize方法
類在呼叫initialize時, 使用的是objc_msgSend訊息機制呼叫

  • 所以, 在呼叫[Person alloc]方法時, 會使用訊息機制呼叫Person類的initialize方法, 又因為Person存在兩個CategoryPerson+Test1Person+Test2, 此時就看哪一個Category最後一個編譯, 就會呼叫裡面的initialize方法
  • 在呼叫[Student alloc]時, 會呼叫Student類的initialize方法, 但是因為此時父類Person還沒有初始化, 所以會先呼叫Personinitialize方法

四、移除子類的+(void)initialize方法, 再次給子類傳送訊息

  • 刪除StudentStudent+Test1Student+Test2中的initialize方法
小碼哥iOS學習筆記第六天: initialize方法
  • 可以看到, Person (Test1) - initialize列印了兩次, 說明Personinitialize方法被呼叫了兩次
  • 前面已經說過, 當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呼叫了父類Personinitialize方法

五、面試題

1、loadinitialize方法的區別是什麼?

  • 呼叫方式
    • load是根據函式地址直接呼叫
    • initialize是通過objc_msgSend呼叫
  • 呼叫時刻
    • loadruntime載入類、分類的時候呼叫(只會呼叫一次)
    • initialize是類第一次接收到訊息的時候呼叫, 每一個類只會initialize一次(如果子類沒有實現initialize方法, 會呼叫父類的initialize方法, 所以父類的initialize方法可能會呼叫多次)

2、load和initializee的呼叫順序

  • load
    • 先呼叫類的load, 在呼叫分類的load
    • 先編譯的類, 優先呼叫load, 呼叫子類的load之前, 會先呼叫父類的load
    • 先編譯的分類, 優先呼叫load
  • initialize
    • 先初始化分類, 後初始化子類
    • 通過訊息機制呼叫, 當子類沒有initialize方法時, 會呼叫父類的initialize方法, 所以父類的initialize方法會呼叫多次

相關文章