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