1. 程式人生 > >【OC底層】Category、+load方法、+initialize方法原理

【OC底層】Category、+load方法、+initialize方法原理

 

 Category原理

- Category編譯之後的底層結構是 struct categroy_t,裡面儲存著分類物件方法、屬性、協議資訊
- 當程式執行時,通過runtime動態的將分類的方法、屬性、協議合併到一個大陣列中
- 底層使用的是二維陣列進行儲存,比如:[[分類2方法列表],[分類1方法列表],[原方法列表]]
- 將合併後的分類資料(方法、屬性、協議)的陣列插入到類原來資料的前面,如上
- 因為它遍歷分類是按倒序遍歷的,所有越後面參與編譯的Category資料,會在陣列的前面

 原始碼的的 categroy_t 定義:

 

下面是runtime原始碼中其中一段程式碼,用來處理分類與原類資料合併的:

 

- 原始碼解讀順序
objc-os.mm
_objc_init
map_images
map_images_nolock

objc-runtime-new.mm
_read_images
remethodizeClass
attachCategories
attachLists
realloc、memmove、 memcpy

 

Category與Class Extension(類擴充套件)的區別:

- 類擴充套件是在編譯時,就會將方法、屬性、協議全合併到一個類檔案中
- 而Category是在執行時,使用runtime動態的將資料合併到類資訊中

 

+load方法原始碼分析

下面是load方法其中一部分原始碼:

 

 

看程式碼可以看出,確實是先呼叫類的load方法,再呼叫分類的load方法,我們看下類的load方法是如果呼叫的,如下:

 

其中:(*load_method)(cls, SEL_load); 就是使用指標方式直接呼叫load方法,不走 objc_msgSend方法

分類的load方法呼叫和上面一樣,原始碼如下:

 

如果大家也想去看原始碼的話,下面是原始碼跟蹤順序,可以瞭解下:

 

+load方法底層實現

呼叫時機:

+load方法會在runtime載入類、分類時呼叫

每個類、分類的+load,在程式執行過程中只調用一次

 

呼叫順序:

1、先執行父類中的load方法
2、先執行原類中的load方法
3、再執行分類中的load方法,按著編譯的反順序,越後編譯越先被執行

 

注意點:

當有多個分類時,每個分類都重寫原類中的一個方法時,那程式呼叫這個方法的時候就會按編譯檔案的順序來判斷,誰在最後就呼叫誰(可以通過專案設定中的Build Phases-->Compile Sources中調整)

分類中的方法不會覆蓋原類中的方法,只是把方法放在了原類方法之前,通過objc_msgSend方法呼叫方法都是找到第一個就呼叫的

原理:是將分類中的方法加入到了之前物件方法列表陣列的前面了,所有找方法的時候會先找到分類中的方法

+load方法例項

建立兩個類,一個父類,一個子類,再分別建立2個父類的分類,2個子類的分類,如下:

 

其中XGPerson是父類,XGStudent是子類,每個類裡面都重寫load,如:

 

XGStudent 也一樣

 

直接執行程式,看日誌輸出如下:

可以看出確實是先呼叫了父類的load再呼叫子類load,然後再呼叫分類的load,那這個分類中的load方法的順序是怎麼樣的?上面已經說過了,就是參與編譯的順序,如下:

 

+initialize原始碼分析

 上面的程式碼就是initialize原始碼的實現,註釋已經寫的很清楚了,這裡主要是遞迴去處理父類

下面這個程式碼和上面是同一個方法裡面的,下面這個才是真正的去呼叫initialize的方法

下面去看下這個callInitialize的實現:

程式碼很簡單,直接就是使用的 objc_msgSend的方法呼叫

 

下面是原始碼解讀的順序:

 

+initialize方法實現

呼叫時機:

類在第一次接到的訊息的時候呼叫,每一個類只會initialize一次,如:[XGPerson alloc],就會呼叫一次,並且後面再 alloc 也不會呼叫

 
呼叫順序:

1、先呼叫父類的initialize
2、再呼叫原類的initialize(如果原類有分類,並且分類重寫initialize,則會呼叫分類中的initialize,當子類沒有initialize,父類可能被呼叫多次)
   按著編譯的反順序,越後編譯越先被執行

 

注意點:

當第一次呼叫子類的方法時,會去判斷是否有父類,並且父類有沒有呼叫過initialize,
如果沒有,則先呼叫父類的,再呼叫子類的

 

+initialize方法例項

同樣使用上面的那2個類和4個分類:

分別重寫initialize方法,和上面一樣,就不一一截圖了:

 

1. 我們使用父類看下會輸出什麼:

輸出日誌:

 可以看到這裡呼叫的是XGPersonPlay的分類,為什麼為呼叫分類的這個方法呢?上面說分類原理的時候也說到了,分類方法和原類方法合併的時候會將分類的方法插入到原類方法之前,只要通過objc_msgSend 方式呼叫方法,就會去這個列表最裡找最先一個找到的方法進行呼叫。因為剛才我們看原碼也知道了 initialize 使用的就是 objc_msgSend 的方式呼叫方法的,所以上面這個就會呼叫分類中的 initialize 方法。

 

2. 我們再來看下,如果使用子類會怎麼樣:

 輸出:

結果也不難理解,上面原始碼裡也看到了,會去先呼叫父類,再去呼叫子類,用的是那個遞迴方式。

 

3. 如果子類和子類的所有分類沒有重寫 initialize 方法,那又會怎麼樣?我們把子類和子類的所有分類的 initialize 方法給註釋掉的輸出結果:

從結果中可以看出,當子類沒有這個方法時,它就會去父類中找這個方法,所以父類的initialize會被呼叫多次,通過ISA指標去找的,之前有說過

 

+initialize和+load的區別

+initialize 是通過objc_msgSend進行呼叫的,所以有以下特點:
 - 如果子類沒有實現+initialize,會呼叫父類的+initialize(所以父類的+initialize可能會被呼叫多次)
 - 如果分類實現了+initialize,就覆蓋類本身的+initialize呼叫,也不能說是真正的覆蓋,只不會是放到原類方法的前面去了

 - 第一次用的時候才會呼叫

+load 是直接通過指標呼叫的,是在runtime載入時就呼叫,無論你用不用它都會呼叫