系統(tǒng)底層源碼分析(7)——Category(分類)的編譯和運(yùn)行

1、什么是 Category(分類)?
分類是 Objective-C 2.0 添加的語言特性,主要作用是為已經(jīng)存在的類添加方法丽旅。
雖然繼承也能為已有類增加新的方法,但有時(shí)繼承關(guān)系會(huì)增加復(fù)雜度纺棺,在運(yùn)行時(shí)榄笙,也無法與父類的原始方法進(jìn)行區(qū)分。

通常 Category(分類)有以下幾種使用場景:

  • 把類的不同實(shí)現(xiàn)方法分開到不同的文件里祷蝌。
  • 聲明私有方法茅撞。
  • 模擬多繼承。
  • framework 私有方法公開化巨朦。

2米丘、Category(分類)和Extension(擴(kuò)展)的區(qū)別是什么?

  • Extension是在編譯階段與該類同時(shí)編譯的罪郊,它的數(shù)據(jù)就已經(jīng)包含在類信息中;
    Category是在運(yùn)行時(shí)尚洽,才會(huì)將數(shù)據(jù)合并到類信息中悔橄。

  • Extension可以直接添加屬性;
    但是Category不可以,只能通過runtime添加癣疟。

  • Extension中聲明的方法只能在該類的 @implementation 中實(shí)現(xiàn)挣柬,所以無法對(duì)系統(tǒng)的類(例如 NSString 類)使用 Extension
    Category可以為系統(tǒng)的類添加方法睛挚。

3邪蛔、為什么 Category(分類)不能像 Extension(擴(kuò)展)一樣添加成員變量?
因?yàn)?Extension是在編譯階段與該類同時(shí)編譯的扎狱,所以作為類的一部分侧到,可以在編譯階段為類添加成員變量淳蔼;
Category是在運(yùn)行時(shí)期間決定的豺妓,成員變量的內(nèi)存布局已經(jīng)在編譯階段確定好了(也就是說objc_class結(jié)構(gòu)體大小是固定的)鞠柄,如果在運(yùn)行時(shí)階段添加成員變量的話兴泥,就會(huì)破壞原有類的內(nèi)存布局骤菠,所以 Category無法直接添加成員變量辣苏,只能通過runtime添加setter/getter達(dá)到添加屬性的效果(屬性和成員變量不一樣)成洗。
另外暗膜,分類的methodList是一個(gè)二維數(shù)組印机,雖沒辦法擴(kuò)展methodLists指向的內(nèi)存區(qū)域矢腻,但可改變methodList指向的內(nèi)存區(qū)域的值(存儲(chǔ)的是指針),所以可以動(dòng)態(tài)添加方法射赛。

從上面我們知道多柑,擴(kuò)展和分類是有區(qū)別的,那么現(xiàn)在就從編譯和運(yùn)行兩個(gè)階段了解一下分類咒劲。

1. 編譯時(shí)期

我們把文件編譯成c++源碼看一下:

.m文件由 OC 轉(zhuǎn) C++ 源碼方法如下:
打開終端顷蟆,執(zhí)行 cd 文件所在目錄 命令,
然后執(zhí)行 clang -rewrite-objc xxx.m 腐魂,
之后xxx.m 所在目錄下就會(huì)生成一個(gè) xxx.cpp 文件帐偎,這就是相關(guān)的 C++ 源碼。

編譯分類文件蛔屹,會(huì)創(chuàng)建一個(gè)_Category_t結(jié)構(gòu)體(如果一個(gè)類有多個(gè)分類削樊,則 Category 數(shù)組中對(duì)應(yīng)多個(gè) Category ),分類的本質(zhì)就是結(jié)構(gòu)體兔毒,存儲(chǔ)著分類的實(shí)例方法漫贞、類方法、屬性育叁、協(xié)議:

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;//屬性列表
};
//Person 類的 Category 結(jié)構(gòu)體賦值
static struct _category_t _OBJC_$_CATEGORY_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "Person", // 類名
    0, // &OBJC_CLASS_$_Person, // cls
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Eat, // 實(shí)例方法列表
    0, // 類方法列表
    0, // 協(xié)議列表
    0, // 屬性列表
};

(從分類結(jié)構(gòu)體看迅脐,分類沒有成員變量列表,成員變量是存放在實(shí)例對(duì)象中的豪嗽,所以分類不能添加成員變量谴蔑,聲明成員變量會(huì)報(bào)錯(cuò)豌骏;但可以聲明屬性。)

運(yùn)行時(shí)隐锭,再把各個(gè)分類的結(jié)構(gòu)體添加合并到原本類中窃躲。所有類的分類都會(huì)放到__objc_catlist列表中:

static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
    &_OBJC_$_CATEGORY_Person_$_Eat,
};
2. 運(yùn)行時(shí)期

Category 是如何被加載的?

  • dyld是蘋果的動(dòng)態(tài)加載器,用來加載 image(注意這里 image 不是指圖片钦睡,而是 Mach-O 格式的二進(jìn)制可執(zhí)行文件)蒂窒。
  • 當(dāng)程序啟動(dòng)時(shí),系統(tǒng)內(nèi)核首先會(huì)加載 dyld 荞怒, 然后 dyld 會(huì)將我們 APP 所依賴的各種庫加載到內(nèi)存空間中洒琢,其中就包括 libobjc 庫(OCruntime), 這些工作,是在 APPmain 函數(shù)執(zhí)行前完成的挣输。
  • 接著通過Runtime加載某個(gè)類的所有Category數(shù)據(jù)纬凤。先把原類數(shù)據(jù)(方法、屬性撩嚼、協(xié)議)的數(shù)組指針后移(后移位置取決于分類大型J俊),再把各個(gè)分類的數(shù)據(jù)(方法完丽、屬性恋技、協(xié)議)合并放到前面,所以當(dāng)對(duì)象調(diào)用分類和類中都有的同名方法時(shí)逻族,先調(diào)用分類的方法(后編譯先調(diào)用)蜻底。

dyld加載的流程大致是這樣:

  1. 配置環(huán)境變量;
  2. 加載共享緩存聘鳞;
  3. 初始化主 APP薄辅;
  4. 插入動(dòng)態(tài)緩存庫;
  5. 鏈接主程序抠璃;
  6. 鏈接插入的動(dòng)態(tài)庫站楚;
  7. 初始化主程序:OC, C++ 全局變量初始化;
  8. 返回主程序入口函數(shù)搏嗡。

{\large\text{作者:行走少年郎 鏈接:http://www.reibang.com/p/b08bbe3613ab 來源:簡書 著作權(quán)歸作者所有窿春。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處采盒。}}

下一篇:Category(分類)加載流程
下下篇:Category(分類)關(guān)聯(lián)屬性

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末旧乞,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子磅氨,更是在濱河造成了極大的恐慌尺栖,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,729評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烦租,死亡現(xiàn)場離奇詭異延赌,居然都是意外死亡货徙,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門皮胡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人赏迟,你說我怎么就攤上這事屡贺。” “怎么了锌杀?”我有些...
    開封第一講書人閱讀 169,461評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵甩栈,是天一觀的道長。 經(jīng)常有香客問我糕再,道長量没,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,135評(píng)論 1 300
  • 正文 為了忘掉前任突想,我火速辦了婚禮殴蹄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘猾担。我一直安慰自己袭灯,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評(píng)論 6 398
  • 文/花漫 我一把揭開白布绑嘹。 她就那樣靜靜地躺著稽荧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪工腋。 梳的紋絲不亂的頭發(fā)上姨丈,一...
    開封第一講書人閱讀 52,736評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音擅腰,去河邊找鬼蟋恬。 笑死,一個(gè)胖子當(dāng)著我的面吹牛惕鼓,可吹牛的內(nèi)容都是我干的筋现。 我是一名探鬼主播,決...
    沈念sama閱讀 41,179評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼箱歧,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼矾飞!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起呀邢,我...
    開封第一講書人閱讀 40,124評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤洒沦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后价淌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體申眼,經(jīng)...
    沈念sama閱讀 46,657評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瞒津,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了括尸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片巷蚪。...
    茶點(diǎn)故事閱讀 40,872評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖濒翻,靈堂內(nèi)的尸體忽然破棺而出屁柏,到底是詐尸還是另有隱情,我是刑警寧澤有送,帶...
    沈念sama閱讀 36,533評(píng)論 5 351
  • 正文 年R本政府宣布淌喻,位于F島的核電站,受9級(jí)特大地震影響雀摘,放射性物質(zhì)發(fā)生泄漏裸删。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評(píng)論 3 336
  • 文/蒙蒙 一阵赠、第九天 我趴在偏房一處隱蔽的房頂上張望涯塔。 院中可真熱鬧,春花似錦清蚀、人聲如沸伤塌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽每聪。三九已至,卻和暖如春齿风,著一層夾襖步出監(jiān)牢的瞬間药薯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評(píng)論 1 274
  • 我被黑心中介騙來泰國打工救斑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留童本,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,304評(píng)論 3 379
  • 正文 我出身青樓脸候,卻偏偏與公主長得像穷娱,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子运沦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容