category的實(shí)現(xiàn)原理
在上一篇文章iOS runtime中提到了class_rw_t這個(gè)結(jié)構(gòu)五慈,在category中的寫的方法蝎土,協(xié)議慨代,屬性等會在程序運(yùn)行經(jīng)由runtime寫入類的class_rw_t中息罗。這是怎么處理的呢疚鲤?
首先得看下category編譯后的結(jié)構(gòu)锥累。新建一個(gè)NSObject的category printCategory.使用clang命令行編譯出category的c++實(shí)現(xiàn)。
NSObject+printCategory.m文件如下
#import "NSObject+printCategory.h"
@implementation NSObject (printCategory)
+(void)printCategory{
NSLog(@"category測試");
}
@end
clang命令行
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk 分類的路徑
得出NSObject+printCategory.cpp文件,cpp文件中代碼較多集歇,抽取其中的關(guān)鍵代碼,我們編寫的category代碼最終幫我們生成了 struct _category_t類型的 _OBJC__CATEGORY_NSObject__printCategory的值
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_NSObject_$_printCategory __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"printCategory", "v16@0:8", (void *)_C_NSObject_printCategory_printCategory}}
};
static struct _category_t _OBJC_$_CATEGORY_NSObject_$_printCategory __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"NSObject",
0, // &OBJC_CLASS_$_NSObject,
0,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_NSObject_$_printCategory,
0,
0,
};
其中結(jié)構(gòu)體_category_t的定義如下
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;//屬性列表
};
在編譯過程中桶略,編譯器把category編譯成上面的內(nèi)容,然后運(yùn)行時(shí)候經(jīng)由runtime把_OBJC__CATEGORY_NSObject__printCategory里面的內(nèi)容合并到類信息class_rw_t這個(gè)結(jié)構(gòu)體中。
對于我們自定義的_OBJC__CATEGORY_NSObject__printCategory這個(gè)category际歼,會把class_methods里面的值惶翻,也就是OBJC_printCategory結(jié)構(gòu)題里面的方法列表粗井,合并到類信息class_rw_t中搜锰,大概的流程圖如下
class_rw_t 中的method_lists是一個(gè)二維數(shù)組,對于數(shù)組的每一行彬檀,存儲一個(gè)文件里面的方法列表巴帮。如類的原有方法列表會存儲到一個(gè)一維數(shù)組里面溯泣,printCategory這個(gè)category里面的方法會存儲到另外一個(gè)一維數(shù)組里面。
當(dāng)把分類的方法合并到method_lists里面時(shí)候榕茧,往method_lists里面插入一個(gè)分類的方法的數(shù)組垃沦。
runtime在合并category的方法列表的時(shí)候,會把類所有的category編譯出來的方法數(shù)組加載出來用押,統(tǒng)計(jì)一共有多少個(gè)一維數(shù)組肢簿,假設(shè)一維數(shù)組的數(shù)量為n,然后把類原有的方法數(shù)組往后挪動n位。再把category里面的方法數(shù)組拷貝到method_lists數(shù)組里面蜻拨。對于拷貝的順序池充,越往后編譯的category方法數(shù)組列表越靠前。
如下圖中NSObject有兩個(gè)category, printCategory 和 printCategory2缎讼。
printCategory排在printCategory2前面收夸,在method_lists列表中數(shù)組的順序printCategory的方法數(shù)組列表比printCategory2的靠前。
當(dāng)我們調(diào)用NSObject 的printCategory方法的時(shí)候血崭,會去method_lists從前往后遍歷查找方法卧惜。當(dāng)在printCategory的分類方法數(shù)組中找到printCategory方法時(shí),會停止查找方法夹纫,并把剛才找到的方法放到類的方法緩存中咽瓷,并執(zhí)行方法。所以即便printCategory2分類中也存在printCategory方法舰讹,也不會執(zhí)行到茅姜。
如果分類中存在與類原有的相同的方法,分類中的方法會“覆蓋”掉類原有的方法月匣。如果有多個(gè)分類中存在同一個(gè)方法匈睁,調(diào)用時(shí)執(zhí)行最后編譯的分類方法。