category是什么?
category是Objective-C語言的一個特性,一般稱之為“分類”,或者“類別”。其作用是在不修改類的源碼的基礎(chǔ)上,給類擴展一些接口。說的通俗一點,就是給已有類添加方法。
當(dāng)需要為一個類添加新的方法時,在Objective-C中有四種方法可以做到。
1、直接在這個類里添加;但是這樣,對于一些封裝的類,會破壞其封裝性,且對于一些系統(tǒng)庫或者第三方動態(tài)庫中的類,無法直接添加方法。
2、通過繼承在子類中添加方法;繼承的開銷太大,且增加了代碼的復(fù)雜度,同時也違背了繼承的初衷,不建議使用。
3、通過協(xié)議,實現(xiàn)擴展一個類的方法;協(xié)議是個好東西,確實很實用,而且還能降低代碼耦合度,但是實現(xiàn)起來過于復(fù)雜,代碼量大,且對于只需要添加一兩個方法時,就顯得有點小題大做了。
4、使用category為類添加方法;一般用于比較簡單的需求,例如僅僅只需要為類添加一兩個特定功能的方法。
這里,我們重點討論一下category的優(yōu)缺點;
優(yōu)點:
1)在不改變一個類的情況下,對一個已存在的類添加新的方法。
2)可以再沒有源代碼的情況下,對框架中的類進(jìn)行擴展。
3)當(dāng)一個類中的代碼量太大時,可以按功能將代碼中的方法放入到不同的category中,減小單個文件的體積。
缺點:
1)類別中的方法的優(yōu)先級高于類中的方法,所以類別中的方法有可能會覆蓋類中的方法。(因此在使用category時,需要注意命名,不要重復(fù)了)
2)不能直接添加成員變量。
3)可以添加屬性,但是不會自動生成getter和setter方法。
category的實現(xiàn)原理
category是Objective-C的語言特性,探討其實現(xiàn)原理,需要從runtime源碼下手,下面就借助源碼簡單分析一下其實現(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ì)都是一個結(jié)構(gòu)體;這個結(jié)構(gòu)體中定義了name、cls、實例方法、類方法、協(xié)議、屬性等等,所以按理說,分類其實是可以為類添加方法、屬性、協(xié)議的,但是不能添加實例變量,因為這個結(jié)構(gòu)體中沒有定義用來存放實力變量的指針變量。
category加載過程
category的加載過程,相對來說就比較復(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);
}
前面幾個函數(shù)的調(diào)用,其實都是準(zhǔn)備工作,最主要的還是:
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
當(dāng)然,我們并不需要去研究dyld的加載過程,所以我們并不關(guān)注這個函數(shù)的具體實現(xiàn),我們關(guān)心的,是這個函數(shù)的第一個參數(shù)map_images。他是一個回調(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);
}
這個函數(shù)比較簡單,就兩個函數(shù)的調(diào)用,第一個lock,猜測應(yīng)該是和線程有關(guān)的,不做深入研究,主要看第二個函數(shù)map_images_nolock。
這個函數(shù)有點長,但是其實我們并不需要對其深入研究,因為這個函數(shù)并不是加載category的細(xì)節(jié),我們找到這個函數(shù)調(diào)用:
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
_read_images函數(shù)也很長,里面有對類、協(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);
}
}
這一段的注釋,其實也說的很明白了:首先,根據(jù)category的目標(biāo)類注冊category,然后,如果目標(biāo)類已經(jīng)實現(xiàn)的話,重新構(gòu)建目標(biāo)類的方法列表。
這段代碼看起來也并不復(fù)雜,其邏輯就是category的實例方法、協(xié)議或者屬性,只要有一個不為空,即調(diào)用addUnattachedCategoryForClass函數(shù)來注冊category,然后判斷category的目標(biāo)類是否實現(xiàn),如果實現(xiàn),就調(diào)用remethodizeClass重新構(gòu)建目標(biāo)類的方法列表rebuild the class's method lists。
很顯然,這里有兩個關(guān)鍵函數(shù):
注冊函數(shù):addUnattachedCategoryForClass;
重新構(gòu)建方法的函數(shù):remethodizeClass;
先看看addUnattachedCategoryForClass函數(shù)的實現(xiàn),探究注冊過程:
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);
}
這個函數(shù)可能看起來有點暈乎,但是仔細(xì)研究,其實也很簡單:
首先,通過unattachedCategories ()取出runtime維護(hù)的MapTable -----category_map,這個MapTable是以TargetClass為key,category_list結(jié)構(gòu)體對象為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ù)組來存儲每一個category_t對象的;這也解釋了為什么一個類可以定義多個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的注冊過程:
判斷以TargetClass為key從MapTable中取出的類型為category_list的list指針是否為空,如果為空,則為list指針申請空間;如果不為空,則在list所占用的空間上,追加申請一個大小為sizeof(list->list[0])的空間,最后,將cat和catHeader存儲到list->list數(shù)組中。
以上就是category的注冊過程,接下來看看方法的重新綁定過程:
/***********************************************************************
* 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);
}
}
這個函數(shù)寥寥數(shù)語,其實也很好搞懂,通過目標(biāo)類名,獲取unattached的category_list,并調(diào)用attachCategories函數(shù),將category_list綁定到目標(biāo)類上。
這個綁定過程,涉及到屬性、協(xié)議以及方法的綁定,這里,我們重點分析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);
這段代碼其實就做了一件事:將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]));
}
}
這里粗略的看看三個if else語句內(nèi)的代碼,其實不難看出,這三個地方,都是在做同一個操作-----將addedLists中的數(shù)據(jù),拷貝到array()->lists。我們第一個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]));
}
首先,計算出新的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])個字節(jié):
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
最后,將addedLists中的數(shù)據(jù)復(fù)制到array()->lists的前addedCount*sizeof(array()->lists[0])個字節(jié)。
至此,整個category的分析過程就算完成了。