- Category編譯以後的底層結構是 struct categroy_t,裏面存儲着分類對象方法、屬性、協議信息
- 當程序運行時,經過runtime動態的將分類的方法、屬性、協議合併到一個大數組中
- 底層使用的是二維數組進行存儲,好比:[[分類2方法列表],[分類1方法列表],[原方法列表]]
- 將合併後的分類數據(方法、屬性、協議)的數組插入到類原來數據的前面,如上
- 由於它遍歷分類是按倒序遍歷的,全部越後面參與編譯的Category數據,會在數組的前面數組
源碼的的 categroy_t 定義:源碼分析
下面是runtime源碼中其中一段代碼,用來處理分類與原類數據合併的:ui
- 源碼解讀順序
objc-os.mm
_objc_init
map_images
map_images_nolockspa
objc-runtime-new.mm
_read_images
remethodizeClass
attachCategories
attachLists
realloc、memmove、 memcpy 3d
- 類擴展是在編譯時,就會將方法、屬性、協議全合併到一個類文件中,不能爲系統類添加擴展
- 而Category是在運行時,使用runtime動態的將數據合併到類信息中,能夠爲系統類添加分類指針
下面是load方法其中一部分源碼:日誌
調用全部load方法對象
看代碼能夠看出,確實是先調用類的load方法,再調用分類的load方法,咱們看下類的load方法是如何調用的,以下:blog
其中:(*load_method)(cls, SEL_load); 就是使用指針方式直接調用load方法,不走 objc_msgSend方法遞歸
分類的load方法調用和上面同樣,源碼以下:
若是你們也想去看源碼的話,下面是源碼跟蹤順序,能夠了解下:
調用時機:
+load方法會在runtime加載類、分類時調用
每一個類、分類的+load,在程序運行過程當中只調用一次
調用順序:
一、先執行父類中的load方法
二、先執行原類中的load方法
三、再執行分類中的load方法,按着編譯的反順序,越後編譯越先被執行
注意點:
當有多個分類時,每一個分類都重寫原類中的一個方法時,那程序調用這個方法的時候就會按編譯文件的順序來判斷,誰在最後就調用誰(能夠經過項目設置中的Build Phases-->Compile Sources中調整)
分類中的方法不會覆蓋原類中的方法,只是把方法放在了原類方法以前,經過objc_msgSend方法調用方法都是找到第一個就調用的
原理:是將分類中的方法加入到了以前對象方法列表數組的前面了,全部找方法的時候會先找到分類中的方法
建立兩個類,一個父類,一個子類,再分別建立2個父類的分類,2個子類的分類,以下:
其中XGPerson是父類,XGStudent是子類,每一個類裏面都重寫load,如:
XGStudent 也同樣
直接運行程序,看日誌輸出以下:
能夠看出確實是先調用了父類的load再調用子類load,而後再調用分類的load,那這個分類中的load方法的順序是怎麼樣的?上面已經說過了,就是參與編譯的順序,以下:
上面的代碼就是initialize源碼的實現,註釋已經寫的很清楚了,這裏主要是遞歸去處理父類
下面這個代碼和上面是同一個方法裏面的,下面這個纔是真正的去調用initialize的方法
下面去看下這個callInitialize的實現:
代碼很簡單,直接就是使用的 objc_msgSend的方法調用
下面是源碼解讀的順序:
調用時機:
類在第一次接到的消息的時候調用,每個類只會initialize一次,如:[XGPerson alloc],就會調用一次,而且後面再 alloc 也不會調用
調用順序:
一、先調用父類的initialize
二、再調用原類的initialize(若是原類有分類,而且分類重寫initialize,則會調用分類中的initialize,當子類沒有initialize,父類可能被調用屢次)
按着編譯的反順序,越後編譯越先被執行
注意點:
當第一次調用子類的方法時,會去判斷是否有父類,而且父類有沒有調用過initialize,
若是沒有,則先調用父類的,再調用子類的
一樣使用上面的那2個類和4個分類:
分別重寫initialize方法,和上面同樣,就不一一截圖了:
1. 咱們使用父類看下會輸出什麼:
輸出日誌:
能夠看到這裏調用的是XGPerson的Play的分類,爲何爲調用分類的這個方法呢?上面說分類原理的時候也說到了,分類方法和原類方法合併的時候會將分類的方法插入到原類方法以前,只要經過objc_msgSend 方式調用方法,就會去這個列表最裏找最早一個找到的方法進行調用。由於剛纔咱們看原碼也知道了 initialize 使用的就是 objc_msgSend 的方式調用方法的,因此上面這個就會調用分類中的 initialize 方法。
2. 咱們再來看下,若是使用子類會怎麼樣:
輸出:
結果也不難理解,上面源碼裏也看到了,會去先調用父類,再去調用子類,用的是那個遞歸方式。
3. 若是子類和子類的全部分類沒有重寫 initialize 方法,那又會怎麼樣?咱們把子類和子類的全部分類的 initialize 方法給註釋掉的輸出結果:
從結果中能夠看出,當子類沒有這個方法時,它就會去父類中找這個方法,因此父類的initialize會被調用屢次,經過ISA指針去找的,以前有說過
+initialize 是經過objc_msgSend進行調用的,因此有如下特色:
- 若是子類沒有實現+initialize,會調用父類的+initialize(因此父類的+initialize可能會被調用屢次)
- 若是分類實現了+initialize,就覆蓋類自己的+initialize調用,也不能說是真正的覆蓋,只不過是放到原類方法的前面去了
- 第一次用的時候纔會調用
+load 是直接經過指針調用的,是在runtime加載時就調用,不管你用不用它都會調用