分類(Category)
Category是Objective-C 2.0之后添加的語(yǔ)言特性表牢,分類煤禽、類別其實(shí)都是指的Category。Category的主要作用是為已經(jīng)存在的類添加方法呢堰。
我們用分類都做了什么事
- 聲明私有方法(我們定義一個(gè)分類,只把頭文件放在對(duì)應(yīng)宿主文件的.m當(dāng)中,滿足私有方法的聲明和使用,對(duì)外不暴露)
- 分解體積龐大的類文件(比如一個(gè)類的功能特別復(fù)雜,那么我們可以按照功能來對(duì)類當(dāng)中的方法進(jìn)行分類,把同一個(gè)功能的方法放在一個(gè)分類文件當(dāng)中)
- 把Framewrok的私有方法公開化
特點(diǎn)
- 運(yùn)行時(shí)決議(我們?cè)诰帉懞梅诸惖奈募?它并沒有把分類的內(nèi)容添加到宿主類上面,這個(gè)時(shí)候宿主類中還沒有分類中的方法,而是在運(yùn)行時(shí)通過runtime把分類中添加的內(nèi)容真實(shí)的添加到宿主類上面)
- 可以為系統(tǒng)類添加分類(比如我們經(jīng)常會(huì)用到獲取UIView坐標(biāo)的分類方法,就是在對(duì)UIView添加了分類)
分類中可以添加哪些內(nèi)容
- 實(shí)例方法
- 類方法
- 協(xié)議
- 屬性(其實(shí)只是聲明了get机久、set方法,并沒有添加實(shí)例變量,可以通過關(guān)聯(lián)對(duì)象來添加實(shí)例變量)
我們來看看分類的底層結(jié)構(gòu)
struct category_t {
const char *name;//分類的名稱
classref_t cls;//分類所屬的宿主類
struct method_list_t *instanceMethods;//實(shí)例方法列表
struct method_list_t *classMethods;//類方法列表
struct protocol_list_t *protocols;//協(xié)議
struct property_list_t *instanceProperties;//實(shí)例屬性列表
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
我們通過分類的結(jié)構(gòu)就可以更好的印證我們能夠?yàn)榉诸愄砑幽男﹥?nèi)容,這里并沒有實(shí)例變量的成員結(jié)構(gòu).
源碼分析
objc-runtime-new.mm
這里主要分析實(shí)例方法的添加邏輯.
我們從 remethodizeClass 這個(gè)方法開始分析
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
runtimeLock.assertWriting();
//判斷當(dāng)前類是否是元類對(duì)象,也就是判斷添加的方法是實(shí)例方法還是類方法
isMeta = cls->isMetaClass();
// 獲取cls中未完成整合的所有分類
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
if (PrintConnecting) {
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
//將cats拼接到cls上
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
來看看attachCategories這個(gè)拼接分類到宿主類的方法
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;//如果當(dāng)前類沒有分類的話直接返回
if (PrintReplacedMethods) printReplacements(cls, cats);
//判斷當(dāng)前類是否是元類對(duì)象
bool isMeta = cls->isMetaClass();
/*
二維數(shù)組 [[method_t,method_t,...],[method_t],[method_t,method_t,method_t],...]
*/
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));//方法列表
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));//屬性列表
protocol_list_t **protolists = (protocol_list_t **)
malloc(cats->count * sizeof(*protolists));//協(xié)議列表
int mcount = 0;//方法
int propcount = 0;//屬性
int protocount = 0;//協(xié)議
int i = cats->count;//宿主類分類的總數(shù)
bool fromBundle = NO;
while (i--) {//倒序遍歷,最先訪問最后遍歷的分類.比如兩個(gè)分類有同名方法,那么最后編譯的那個(gè)同名方法會(huì)最終生效
//獲取一個(gè)分類
auto& entry = cats->list[i];
//獲取該分類的方法列表
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
//最后編譯的分類最先添加到分類列表中
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
//屬性列表添加規(guī)則,同方法列表添加規(guī)則
property_list_t *proplist = entry.cat->propertiesForMeta(isMeta);
if (proplist) {
proplists[propcount++] = proplist;
}
//協(xié)議列表添加規(guī)則,同方法列表添加規(guī)則
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
// 獲取宿主類當(dāng)中的rw數(shù)據(jù),其中包含宿主類的方法列表信息
auto rw = cls->data();
// 主要是針對(duì) 分類中有關(guān)于內(nèi)存管理相關(guān)方法情況下的一些特殊處理
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
/*
methods代表類的方法列表,attachLists方法表示將mcount個(gè)元素的mlists(方法列表)拼接到rw(宿主類)的methods(方法列表)上
*/
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
rw->properties.attachLists(proplists, propcount);
free(proplists);
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
演示圖例
添加分類的流程
- 獲取分類列表的count,然后原來的類方法列表內(nèi)存移動(dòng)count
- 分類列表內(nèi)存拷貝到原來的類方法列表的前方
- 同樣的方法藏鹊,優(yōu)先調(diào)用分類的方法
- 分類具有同樣的方法润讥,根據(jù)編譯順序決定,取最后編譯分類的方法列表
拓展(Extension)
我們用拓展都做了什么事
- 聲明私有屬性
- 聲明私有方法
- 聲明私有成員變量
拓展的特定
- 編譯時(shí)決議
- 只以聲明存在,沒有具體實(shí)現(xiàn)盘寡,寄生于宿主類.m中
- 不能為系統(tǒng)類添加擴(kuò)展
分類楚殿、擴(kuò)展區(qū)別
- 分類中原則上只能增加方法(能添加屬性的的原因只是通過runtime解決無(wú)setter/getter的問題而已)
- 類擴(kuò)展不僅可以增加方法,還可以增加實(shí)例變量(或者屬性)竿痰,只是該實(shí)例變量默認(rèn)是@private類型的(用范圍只能在自身類脆粥,而不是子類或其他地方)
- 類擴(kuò)展中聲明的方法沒被實(shí)現(xiàn),編譯器會(huì)報(bào)警影涉,但是類別中的方法沒被實(shí)現(xiàn)編譯器是不會(huì)有任何警告的变隔。這是因?yàn)轭悢U(kuò)展是在編譯階段被添加到類中,而類別是在運(yùn)行時(shí)添加到類中蟹倾。
- 類擴(kuò)展不能像分類那樣擁有獨(dú)立的實(shí)現(xiàn)部分(@implementation部分)匣缘,也就是說猖闪,類擴(kuò)展所聲明的方法必須依托對(duì)應(yīng)類的實(shí)現(xiàn)部分來實(shí)現(xiàn)。