category是什么?
category
是Objective-C
語言的一個(gè)特性奸鸯,一般稱之為“分類”矢棚,或者“類別”。其作用是在不修改類的源碼的基礎(chǔ)上府喳,給類擴(kuò)展一些接口。說的通俗一點(diǎn)蘑拯,就是給已有類添加方法钝满。
當(dāng)需要為一個(gè)類添加新的方法時(shí),在Objective-C中有四種方法可以做到申窘。
1弯蚜、直接在這個(gè)類里添加;但是這樣剃法,對(duì)于一些封裝的類碎捺,會(huì)破壞其封裝性,且對(duì)于一些系統(tǒng)庫(kù)或者第三方動(dòng)態(tài)庫(kù)中的類贷洲,無法直接添加方法收厨。
2、通過繼承在子類中添加方法优构;繼承的開銷太大诵叁,且增加了代碼的復(fù)雜度,同時(shí)也違背了繼承
的初衷钦椭,不建議使用拧额。
3碑诉、通過協(xié)議,實(shí)現(xiàn)擴(kuò)展一個(gè)類的方法侥锦;協(xié)議是個(gè)好東西进栽,確實(shí)很實(shí)用,而且還能降低代碼耦合度恭垦,但是實(shí)現(xiàn)起來過于復(fù)雜快毛,代碼量大,且對(duì)于只需要添加一兩個(gè)方法時(shí)署照,就顯得有點(diǎn)小題大做了祸泪。
4、使用category為類添加方法建芙;一般用于比較簡(jiǎn)單的需求没隘,例如僅僅只需要為類添加一兩個(gè)特定功能的方法。
這里禁荸,我們重點(diǎn)討論一下category的優(yōu)缺點(diǎn)
;
優(yōu)點(diǎn):
1)在不改變一個(gè)類的情況下右蒲,對(duì)一個(gè)已存在的類添加新的方法。
2)可以再?zèng)]有源代碼的情況下赶熟,對(duì)框架中的類進(jìn)行擴(kuò)展瑰妄。
3)當(dāng)一個(gè)類中的代碼量太大時(shí),可以按功能將代碼中的方法放入到不同的category中映砖,減小單個(gè)文件的體積间坐。
缺點(diǎn):
1)類別中的方法的優(yōu)先級(jí)高于類中的方法,所以類別中的方法有可能會(huì)覆蓋類中的方法邑退。(因此在使用category時(shí)竹宋,需要注意命名,不要重復(fù)了)
2)不能直接添加成員變量地技。
3)可以添加屬性蜈七,但是不會(huì)自動(dòng)生成getter和setter方法。
category的實(shí)現(xiàn)原理
category是Objective-C的語言特性莫矗,探討其實(shí)現(xiàn)原理飒硅,需要從runtime源碼下手,下面就借助源碼簡(jiǎn)單分析一下其實(shí)現(xiàn)的原理吧作谚。
先找到category的定義:
#if __OBJC2__
typedef struct category_t *Category;
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
很明顯三娩,Category和Class一樣,其本質(zhì)都是一個(gè)結(jié)構(gòu)體妹懒;這個(gè)結(jié)構(gòu)體中定義了name尽棕、cls、實(shí)例方法彬伦、類方法滔悉、協(xié)議伊诵、屬性等等,所以按理說回官,分類其實(shí)是可以為類添加方法曹宴、屬性、協(xié)議的歉提,但是不能添加實(shí)例變量笛坦,因?yàn)檫@個(gè)結(jié)構(gòu)體中沒有定義用來存放實(shí)力變量的指針變量。
category加載過程
category的加載過程苔巨,相對(duì)來說就比較復(fù)雜版扩。需要從objc的初始化開始說。
來到故事最開始的地方_objc_init
:
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
lock_init();
exception_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
前面幾個(gè)函數(shù)的調(diào)用侄泽,其實(shí)都是準(zhǔn)備工作礁芦,最主要的還是:
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
當(dāng)然,我們并不需要去研究dyld的加載過程悼尾,所以我們并不關(guān)注這個(gè)函數(shù)的具體實(shí)現(xiàn)柿扣,我們關(guān)心的,是這個(gè)函數(shù)的第一個(gè)參數(shù)map_images
闺魏。他是一個(gè)回調(diào)函數(shù)指針未状,在* objc-runtime-new.mm
中可以找到它:
void
map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
mutex_locker_t lock(runtimeLock);
return map_images_nolock(count, paths, mhdrs);
}
這個(gè)函數(shù)比較簡(jiǎn)單,就兩個(gè)函數(shù)的調(diào)用析桥,第一個(gè)lock
司草,猜測(cè)應(yīng)該是和線程有關(guān)的,不做深入研究泡仗,主要看第二個(gè)函數(shù)map_images_nolock
埋虹。
這個(gè)函數(shù)有點(diǎn)長(zhǎng),但是其實(shí)我們并不需要對(duì)其深入研究沮焕,因?yàn)檫@個(gè)函數(shù)并不是加載category的細(xì)節(jié),我們找到這個(gè)函數(shù)調(diào)用:
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
_read_images
函數(shù)也很長(zhǎng)拉宗,里面有對(duì)類峦树、協(xié)議等的加載過程,當(dāng)然category的加載也在旦事,我們只需要找到關(guān)于category
的那部分即可:
// Process this category.
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
這一段的注釋魁巩,其實(shí)也說的很明白了:首先,根據(jù)category的目標(biāo)類注冊(cè)category姐浮,然后谷遂,如果目標(biāo)類已經(jīng)實(shí)現(xiàn)的話,重新構(gòu)建目標(biāo)類的方法列表
卖鲤。
這段代碼看起來也并不復(fù)雜肾扰,其邏輯就是category的實(shí)例方法畴嘶、協(xié)議或者屬性,只要有一個(gè)不為空集晚,即調(diào)用addUnattachedCategoryForClass
函數(shù)來注冊(cè)category窗悯,然后判斷category的目標(biāo)類是否實(shí)現(xiàn),如果實(shí)現(xiàn)偷拔,就調(diào)用remethodizeClass
重新構(gòu)建目標(biāo)類的方法列表rebuild the class's method lists
蒋院。
很顯然,這里有兩個(gè)關(guān)鍵函數(shù):
注冊(cè)函數(shù):addUnattachedCategoryForClass
;
重新構(gòu)建方法的函數(shù):remethodizeClass
莲绰;
先看看addUnattachedCategoryForClass
函數(shù)的實(shí)現(xiàn)欺旧,探究注冊(cè)過程:
static void addUnattachedCategoryForClass(category_t *cat, Class cls,
header_info *catHeader)
{
runtimeLock.assertLocked();
// DO NOT use cat->cls! cls may be cat->cls->isa instead
NXMapTable *cats = unattachedCategories();
category_list *list;
list = (category_list *)NXMapGet(cats, cls);
if (!list) {
list = (category_list *)
calloc(sizeof(*list) + sizeof(list->list[0]), 1);
} else {
list = (category_list *)
realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
}
list->list[list->count++] = (locstamped_category_t){cat, catHeader};
NXMapInsert(cats, cls, list);
}
這個(gè)函數(shù)可能看起來有點(diǎn)暈乎,但是仔細(xì)研究蛤签,其實(shí)也很簡(jiǎn)單:
首先辞友,通過unattachedCategories ()
取出runtime維護(hù)的MapTable
-----category_map
,這個(gè)MapTable
是以TargetClass
為key顷啼,category_list
結(jié)構(gòu)體對(duì)象為value的表踏枣,而category_list
結(jié)構(gòu)體內(nèi),又定義了locstamped_category_t
結(jié)構(gòu)體數(shù)組钙蒙,locstamped_category_t
結(jié)構(gòu)體中有定義了category_t
結(jié)構(gòu)體指針:
struct locstamped_category_t {
category_t *cat;
struct header_info *hi;
};
struct locstamped_category_list_t {
uint32_t count;
#if __LP64__
uint32_t reserved;
#endif
locstamped_category_t list[0];
};
typedef locstamped_category_list_t category_list;
這就說明了category_list
是通過locstamped_category_t
結(jié)構(gòu)體數(shù)組來存儲(chǔ)每一個(gè)category_t
對(duì)象的茵瀑;這也解釋了為什么一個(gè)類可以定義多個(gè)category
。
而這段代碼:
if (!list) {
list = (category_list *)
calloc(sizeof(*list) + sizeof(list->list[0]), 1);
} else {
list = (category_list *)
realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
}
list->list[list->count++] = (locstamped_category_t){cat, catHeader};
}
則說明了category
的注冊(cè)過程:
判斷以TargetClass
為key從MapTable
中取出的類型為category_list
的list
指針是否為空躬厌,如果為空马昨,則為list
指針申請(qǐng)空間;如果不為空扛施,則在list
所占用的空間上鸿捧,追加申請(qǐng)一個(gè)大小為sizeof(list->list[0])
的空間,最后疙渣,將cat
和catHeader
存儲(chǔ)到list->list
數(shù)組中匙奴。
以上就是category的注冊(cè)過程,接下來看看方法的重新綁定過程:
/***********************************************************************
* remethodizeClass
* Attach outstanding categories to an existing class.
* Fixes up cls's method list, protocol list, and property list.
* Updates method caches for cls and its subclasses.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
runtimeLock.assertLocked();
isMeta = cls->isMetaClass();
// Re-methodizing: check for more categories
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
if (PrintConnecting) {
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
這個(gè)函數(shù)寥寥數(shù)語妄荔,其實(shí)也很好搞懂泼菌,通過目標(biāo)類名,獲取unattached
的category_list
啦租,并調(diào)用attachCategories
函數(shù)哗伯,將category_list
綁定到目標(biāo)類上。
這個(gè)綁定過程篷角,涉及到屬性焊刹、協(xié)議以及方法的綁定,這里,我們重點(diǎn)分析category
的方法重新綁定過程:
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
......
int mcount = 0;
bool fromBundle = NO;
int i = cats->count;
while (i--) {
auto& entry = cats->list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
......
}
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount);
free(mlists);
這段代碼其實(shí)就做了一件事:將cats->list
數(shù)組中的數(shù)據(jù)取出虐块,并存放到mlists
數(shù)組中俩滥,
然后將mlists
作為參數(shù),傳遞以給attachLists
函數(shù)非凌,當(dāng)然举农,同樣作為參數(shù)的還有數(shù)組的count
。
所以咱們還得繼續(xù)跟進(jìn)到attachLists
函數(shù)中:
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
這里粗略的看看三個(gè)if else
語句內(nèi)的代碼敞嗡,其實(shí)不難看出颁糟,這三個(gè)地方,都是在做同一個(gè)操作-----將addedLists
中的數(shù)據(jù)喉悴,拷貝到array()->lists
棱貌。我們第一個(gè)if
拿出來分析一下:
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
首先,計(jì)算出新的array()->count
:
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
然后箕肃,用新的array()->count
婚脱,為array()->lists
分配內(nèi)存:
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
然后,將array()->lists
中原來的數(shù)據(jù)勺像,往后移addedCount*sizeof(array()->lists[0])
個(gè)字節(jié):
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
最后障贸,將addedLists
中的數(shù)據(jù)復(fù)制到array()->lists
的前addedCount*sizeof(array()->lists[0])
個(gè)字節(jié)。
至此吟宦,整個(gè)category的分析過程就算完成了篮洁。