1. Category的底層結(jié)構(gòu)
通過runtime動態(tài)的將分類的方法合并到類對象或元類對象中,程序編譯的時:category會生成,category中的信息會存儲在struct _category_t中.
struct _category_t {
const char *name;//類名
struct _class_t *cls;
const struct _method_list_t *instance_methods;//實例方法
const struct _method_list_t *class_methods;//類方法
const struct _protocol_list_t *protocols;//協(xié)議
const struct _prop_list_t *properties;//屬性
};
- 從結(jié)構(gòu)體可以知道,有屬性列表,所以分類可以聲明屬性,但是分類只會生成該屬性對應(yīng)的get和set的聲明,沒有去實現(xiàn)該方法。
- 結(jié)構(gòu)體沒有成員變量列表,所以不能聲明成員變量。
- 分類并不會改變原有類的內(nèi)存分布的情況,它是在運行期間決定的,此時內(nèi)存的分布已經(jīng)確定,若此時再添加實例會改變內(nèi)存的分布情況,這對編譯性語言是災(zāi)難,是不允許的。
category的源碼閱讀軌跡:
- objc-os.mm
- _objc_init
- map_images
- map_images_nolock
- objc-runtime-new.mm
- _read_images
- remethodizeClass
- attachCategories
- attachLists
- realloc、memmove、 memcpy
category的源碼分析
① readimges 是讀取模塊的意思,參數(shù)有totalClass是所有的類的意思.
②.remethodizeClass是重新組織類的方法的意思
③.attachCategories是將分類重新規(guī)整,參數(shù)有兩個:一個類名,一個是分類的數(shù)組,在內(nèi)部有一個方法數(shù)組的二維數(shù)組,一個屬性數(shù)組的二維數(shù)組,一個協(xié)議的二維數(shù)組,把所有Category的方法、屬性、協(xié)議數(shù)據(jù),合并到一個大數(shù)組中.(attachCategories)
④.attachLists有兩個參數(shù)一個所有category的方法列表或?qū)傩粤斜砘騾f(xié)議列表的數(shù)組和第二個參數(shù)傳入的數(shù)組的count,realloc重新計算方法列表分配的內(nèi)存大小,memmove移動原來的方法數(shù)據(jù)到末尾,memcpy分類的數(shù)據(jù)插入到原來數(shù)據(jù)的前面.
順序的總體的概括:
- 通過Runtime加載某個類的所有Category數(shù)據(jù).
- 把所有Category的方法、屬性、協(xié)議數(shù)據(jù),合并到一個大數(shù)組中
后面參與編譯的Category數(shù)據(jù),會在數(shù)組的前面.- 將合并后的分類數(shù)據(jù)(方法、屬性、協(xié)議),插入到類原來數(shù)據(jù)的前面.
category memmove到memcpy的過程流程圖:
代碼例子:
// 原來的類和分類看Demo,這里就不列舉出來了
// 開始調(diào)用
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
Person *person = [[Person alloc] init];
[person run];
[person test];
[person eat];
// 通過runtime動態(tài)將分類的方法合并到類對象、元類對象zhong
}
return 0;
}
通過運行結(jié)果可知,分類方法會覆蓋原來類對象方法,并且最后參與編譯的會在調(diào)用順序最前面。(其實不是覆蓋,只是尋找方法時的順序.)
這里有2個關(guān)于category的問題
1.分類中能不能添加屬性,為什么?
不能。類的內(nèi)存布局在編譯時期就已經(jīng)確定了,category是運行時才加載的早已經(jīng)確定了內(nèi)存布局所以無法添加實例變量,如果添加實例變量就會破壞category的內(nèi)部布局。
2.為什么說category是在運行時加載的?不能添加實例變量,那為什么能添加屬性?
根據(jù)category_t的結(jié)構(gòu)
①.category小括號里寫的名字
②.要擴展的類對象,編譯期間這個值是不會有的,在app被runtime加載時才會根據(jù)name對應(yīng)到類對象
③.這個category所有的-方法
④.這個category所有的+方法
⑤.這個category實現(xiàn)的protocol,比較不常用在category里面實現(xiàn)協(xié)議,但是確實支持的
⑥.這個category所有的property,這也是category里面可以定義屬性的原因,不過這個property不會@synthesize實例變量,一般有需求添加實例變量屬性時會采用objc_setAssociatedObject和objc_getAssociatedObject方法綁定方法綁定,不過這種方法生成的與一個普通的實例變量完全是兩碼事。
2. +load方法和initialize方法
一. +load
+load方法會在runtime加載類、分類時調(diào)用,并且只調(diào)用一次.
- load方法的調(diào)用順序
- 先調(diào)用類的+load.
- 按照編譯先后順序調(diào)用(先編譯,先調(diào)用).
- 調(diào)用子類的+load之前會先調(diào)用父類的+load.
- 再調(diào)用分類的+load.
- 按照編譯先后順序調(diào)用(先編譯,先調(diào)用)
- load方法為什么類和分類都調(diào)用,原因是load方法是根據(jù)方法地址直接調(diào)用.
代碼佐證:
- objc4源碼解讀過程:objc-os.mm
- _objc_init
- load_images
- prepare_load_methods
schedule_class_load
add_class_to_loadable_list
add_category_to_loadable_list- call_load_methods
call_class_loads
call_category_loads
(*load_method)(cls, SEL_load)
+load方法是根據(jù)方法地址直接調(diào)用,并不是經(jīng)過objc_msgSend函數(shù)調(diào)用。
二. +initialize方法
+initialize方法會在類第一次接收到消息時調(diào)用.(initialize應(yīng)該是objc_msgSend實現(xiàn)的)
- initialize調(diào)用順序:
- 先調(diào)用父類的+initialize,再調(diào)用子類的+initialize.
- (先初始化父類,再初始化子類,每個類只會初始化1次).
- 如果分類實現(xiàn)了+initialize,就覆蓋類本身的+initialize調(diào)用.
- objc4源碼解讀過程
- objc-msg-arm64.s
objc_msgSend- objc-runtime-new.mm
class_getInstanceMethod
lookUpImpOrNil
lookUpImpOrForward
_class_initialize
callInitialize
objc_msgSend(cls, SEL_initialize)
調(diào)用的詳情解析:
①.class_getInstanceMethod這個是找到對象方法的方法.
②.lookUpImporNil.
③.lookUpImporForward在查找方法的時候調(diào)用_class_initialize看類是否初始化,如果沒有初始化就調(diào)用callInitialize初始化.
④._class_initialize 是一個遞歸來判斷父類是否初始化.
調(diào)用順序證明:
解析:
1.[Student alloc]會調(diào)用+initialize方法,因為他有父類Person,所以先調(diào)用Person的+initialize方法,又因為分類在前面,所以調(diào)用了Person(Test2)的+initialize方法。但是他自己本身沒有實現(xiàn)+initialize方法,所以會去父類查找,然后分類方法在前面,所以調(diào)用了Person(Test2)的+initialize方法。
2.[Teacher alloc]會調(diào)用+initialize方法,因為他有父類Person,所以先調(diào)用Person的+initialize方法,但是前面已經(jīng)初始化過了,所以跳過,調(diào)用自己的+initialize方法,但是因為他自己沒有實現(xiàn)+initialize方法,所以調(diào)用父類的+initialize方法,又因為分類方法在前面,所以調(diào)用Person(Test) +initialize方法。
3.[Person alloc],因為前面已經(jīng)初始化過了,所以不會再調(diào)+initialize方法,所以這里不打印。
+initialize和+load的很大區(qū)別是,+initialize是通過objc_msgSend進行調(diào)用的,所以有以下特點:
- 如果子類沒有實現(xiàn)+initialize,會調(diào)用父類的+initialize(所以父類的+initialize可能會被調(diào)用多次).
- 如果分類實現(xiàn)了+initialize,就覆蓋類本身的+initialize調(diào)用.
想了解更多iOS學(xué)習(xí)知識請聯(lián)系:QQ(814299221)