分類在我們的項(xiàng)目中經(jīng)常被使用到,它是給現(xiàn)有的類添加方法,也可用于根據(jù)功能劃分模塊,今天我們就來研究一下分類實(shí)現(xiàn)的原理.
?比如說有如下3個類,思考一下如下圖所示的實(shí)例方法存放在哪里?

我們之前研究OC對象的本質(zhì)時已經(jīng)知道了,實(shí)例方法存放在類對象中,類方法存放在元類對象中,也就是說
- thought:方法存放在HHPerson類對象中.那么分類中的方法存放在哪里呢?莫非-eat:方法存放在HHPerson+eat類對象中,-run:方法存放在HHPerson+run類對象中?事實(shí)上,一個類在內(nèi)存中只存在一個類對象,
-thought:,-eat:,-run:這三個實(shí)例方法在編譯期都存放在struct _category_t這個結(jié)構(gòu)體中,等到運(yùn)行期利用runtime機(jī)制動態(tài)合并到HHPerson這個類對象中.
下面我們就來驗(yàn)證一下上面的結(jié)論.
-
一:窺探Category 底層數(shù)據(jù)結(jié)構(gòu)
我們寫一個分類HHPerson+eat,然后轉(zhuǎn)換成.cpp文件,發(fā)現(xiàn)Category底層被轉(zhuǎn)換為struct _category_t這種類型的結(jié)構(gòu)體:
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;//屬性列表
};
我們的分類HHPerson+eat被轉(zhuǎn)換為類型為static struct _category_t,變量名為:_OBJC_$_CATEGORY_HHPerson_$_eat:
static struct _category_t _OBJC_$_CATEGORY_HHPerson_$_eat __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"HHPerson",
0, // &OBJC_CLASS_$_HHPerson,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_HHPerson_$_eat,
0,
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_HHPerson_$_eat,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_HHPerson_$_eat,
};

對比圖
實(shí)例方法列表:
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_HHPerson_$_eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"eat", "v16@0:8", (void *)_I_HHPerson_eat_eat}}
};
協(xié)議列表: (HHPerson+eat實(shí)現(xiàn)了NSCopying,NSCoding協(xié)議)
static struct /*_protocol_list_t*/ {
long protocol_count; // Note, this is 32/64 bit
struct _protocol_t *super_protocols[2];
} _OBJC_CATEGORY_PROTOCOLS_$_HHPerson_$_eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
2,
&_OBJC_PROTOCOL_NSCopying,
&_OBJC_PROTOCOL_NSCoding
};
屬性列表:
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_HHPerson_$_eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{{"age","Ti,N"}}
};
以上就是分類的底層結(jié)構(gòu),可以看到,分類的信息在編譯期間都被分離出來了,下面我們從runtime源碼研究一下分類.
-
二:從Runtime源碼查看Category
打開runtime源碼,按照以下步驟查看:
- 搜索并打開
objc-os.mm源文件,找到void _objc_init(void)方法.此方法是Runtime初始化入口. - 點(diǎn)擊進(jìn)入
map_images:
- 點(diǎn)擊進(jìn)入
map_images_nolock:
- 點(diǎn)擊進(jìn)入
_read_images(現(xiàn)在開始已經(jīng)進(jìn)入加載模塊):
- 在
_read_images中找到// Discover categories.(搜索分類),我們重點(diǎn)研究這里:
檢索分類
- category_t **catlist 是一個二位數(shù)組,里面存放的使我們給一個類創(chuàng)建的所有分類,比如上面我們給
HHPerson類添加了三個分類HHPerson +eat,HHPerson +run,HHPerson +thought就存放在這個二維數(shù)組中:[[category_t_eat],[category_t_run],[category_t_thought]]. -
remethodizeClass();重新組織方法,傳入類對象就是重新組織實(shí)例方法,傳入cls->ISA()就是重新組織類方法.
- 點(diǎn)擊進(jìn)入
remethodizeClass(),找到attachCategories(cls, cats, true /*flush caches*/);
附加分類 - 點(diǎn)擊進(jìn)入
attachCategories(cls, cats, true /*flush caches*/);:
attachCategories方法內(nèi)部邏輯
我們梳理一下attachCategories (cls,cats,true)方法.attach一詞是附加的意思,從名字上我們可以看出這個方法大概意思是:附加分類.事實(shí)上它的確如此,下面我開始研究:
- 參數(shù)分析:
-
cls, 分類的本類,就是我們現(xiàn)在HHPerson -
cats, 分類列表,[category_t (HHPerson+run),category_t (HHPerson+eat), category_t (HHPerson+thought)].
- 首先分配內(nèi)存,創(chuàng)建三個二維數(shù)組
mlists,proplists,protolists分別用來存放方法列表,屬性列表,協(xié)議列表 - 通過while循環(huán)遍歷分類列表
cats,取出某一個分類.再取出分類中的方法列表,屬性列表,協(xié)議列表,分別存放到mlists,proplists,protolists二維數(shù)組中. - 取出
class的rw,rw中存放著類的方法,屬性,協(xié)議,成員變量等信息. - 分別調(diào)用
rw的
rw->methods.attachLists(mlists, mcount),
rw->properties.attachLists(proplists, propcount),
rw->protocols.attachLists(protolists, protocount),
把分類中的方法列表,屬性列表,協(xié)議列表歸并到本類中.
本篇主要講了category的底層數(shù)據(jù)結(jié)構(gòu),和分析runtime如何處理category分類信息的.在下篇文章中--Category實(shí)現(xiàn)的原理二:分類信息如何添加到本類中將介紹runtime如何將分類信息添加到本類中.





