Category實(shí)現(xiàn)的原理一:底層結(jié)構(gòu)及源碼分析

分類在我們的項(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源碼,按照以下步驟查看:
  1. 搜索并打開objc-os.mm源文件,找到void _objc_init(void)方法.此方法是Runtime初始化入口.
  2. 點(diǎn)擊進(jìn)入map_images:
  3. 點(diǎn)擊進(jìn)入map_images_nolock:
  4. 點(diǎn)擊進(jìn)入_read_images(現(xiàn)在開始已經(jīng)進(jìn)入加載模塊):
  5. _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()就是重新組織類方法.
  1. 點(diǎn)擊進(jìn)入remethodizeClass(),找到attachCategories(cls, cats, true /*flush caches*/);
    附加分類
  2. 點(diǎn)擊進(jìn)入attachCategories(cls, cats, true /*flush caches*/);:
    attachCategories方法內(nèi)部邏輯

我們梳理一下attachCategories (cls,cats,true)方法.attach一詞是附加的意思,從名字上我們可以看出這個方法大概意思是:附加分類.事實(shí)上它的確如此,下面我開始研究:

  1. 參數(shù)分析:
  • cls , 分類的本類,就是我們現(xiàn)在HHPerson
  • cats , 分類列表,[category_t (HHPerson+run),category_t (HHPerson+eat), category_t (HHPerson+thought)].
  1. 首先分配內(nèi)存,創(chuàng)建三個二維數(shù)組mlists,proplists,protolists分別用來存放方法列表,屬性列表,協(xié)議列表
  2. 通過while循環(huán)遍歷分類列表cats,取出某一個分類.再取出分類中的方法列表,屬性列表,協(xié)議列表,分別存放到mlists,proplists,protolists二維數(shù)組中.
  3. 取出classrw,rw中存放著類的方法,屬性,協(xié)議,成員變量等信息.
  4. 分別調(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如何將分類信息添加到本類中.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容