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
庫(OC
和runtime
), 這些工作,是在APP
的main
函數(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加載的流程大致是這樣:
- 配置環(huán)境變量;
- 加載共享緩存聘鳞;
- 初始化主 APP薄辅;
- 插入動(dòng)態(tài)緩存庫;
- 鏈接主程序抠璃;
- 鏈接插入的動(dòng)態(tài)庫站楚;
- 初始化主程序:OC, C++ 全局變量初始化;
- 返回主程序入口函數(shù)搏嗡。