Category原理 - Category編譯之後的底層結構是 struct categroy_t,裡面存儲著分類對象方法、屬性、協議信息- 當程式運行時,通過runtime動態的將分類的方法、屬性、協議合併到一個大數組中- 底層使用的是二維數組進行存儲,比如:[[分類2方法列表],[分類1方法列表 ...
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. 我們使用父類看下會輸出什麼:
輸出日誌:
可以看到這裡調用的是XGPerson的Play的分類,為什麼為調用分類的這個方法呢?上面說分類原理的時候也說到了,分類方法和原類方法合併的時候會將分類的方法插入到原類方法之前,只要通過objc_msgSend 方式調用方法,就會去這個列表最里找最先一個找到的方法進行調用。因為剛纔我們看原碼也知道了 initialize 使用的就是 objc_msgSend 的方式調用方法的,所以上面這個就會調用分類中的 initialize 方法。
2. 我們再來看下,如果使用子類會怎麼樣:
輸出:
結果也不難理解,上面源碼里也看到了,會去先調用父類,再去調用子類,用的是那個遞歸方式。
3. 如果子類和子類的所有分類沒有重寫 initialize 方法,那又會怎麼樣?我們把子類和子類的所有分類的 initialize 方法給註釋掉的輸出結果:
從結果中可以看出,當子類沒有這個方法時,它就會去父類中找這個方法,所以父類的initialize會被調用多次,通過ISA指針去找的,之前有說過
+initialize和+load的區別
+initialize 是通過objc_msgSend進行調用的,所以有以下特點:
- 如果子類沒有實現+initialize,會調用父類的+initialize(所以父類的+initialize可能會被調用多次)
- 如果分類實現了+initialize,就覆蓋類本身的+initialize調用,也不能說是真正的覆蓋,只不會是放到原類方法的前面去了
- 第一次用的時候才會調用
+load 是直接通過指針調用的,是在runtime載入時就調用,無論你用不用它都會調用