Category用于向已經(jīng)存在的類添加方法從而達到擴展已有類的目的巩梢,在很多情形下Category也是比創(chuàng)建子類更優(yōu)的選擇逸尖。Category用于大型類有效分解。新添加的方法會被被擴展的類的所有子類自動繼承煮仇。Category也可以用于替代這個已有類中某個方法的實體倘零,從而達到修復BUG的目的。如此就不能去調用已有類中原有的那個被替換掉方法實體了幻梯。需要注意的是兜畸,當準備有Category來替換某一個方法的時候,一定要保證實現(xiàn)原來方法的所有功能碘梢,否則這種替代就是沒有意義而且會引起新的BUG咬摇。
Category的方法不一定非要在@implementation中實現(xiàn),也可以在其他位置實現(xiàn)煞躬,但是當調用Category的方法時肛鹏,依據(jù)繼承樹沒有找到該方法的實現(xiàn),程序則會崩潰恩沛。Category理論上不能添加變量在扰,但是可以使用@dynamic 來彌補這種不足。
@implementation NSObject (Category)
@dynamic variable;
- (id) variable
{
return objc_getAssociatedObject(self, externVariableKey);
}
- (void)setVariable:(id) variable
{
objc_setAssociatedObject(self, externVariableKey, variable,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
和子類不同的是雷客,Category不能用于向被擴展類添加實例變量芒珠。Category通常作為一種組織框架代碼的工具來使用。如果需要添加一個新的變量佛纫,則需添加子類妓局。如果只是添加一個新的方法,用Category是比較好的選擇呈宇。
runtime對category的加載過程
下面是runtime中category的結構:
struct _category_t {
const char *name; // 類的名字
struct _class_t *cls; // 要擴展的類對象好爬,編譯期間這個值是不會有的,在app被runtime加載時才會根據(jù)name對應到類對象
const struct _method_list_t *instance_methods; // 實例方法
const struct _method_list_t *class_methods; // 類方法
const struct _protocol_list_t *protocols; // 這個category實現(xiàn)的protocol甥啄,比較不常用在category里面實現(xiàn)協(xié)議存炮,但是確實支持的
const struct _prop_list_t *properties; // 這個category所有的property,這也是category里面可以定義屬性的原因蜈漓,不過這個property不會@synthesize實例變量穆桂,一般有需求添加實例變量屬性時會采用objc_setAssociatedObject和objc_getAssociatedObject方法綁定方法綁定,不過這種方法生成的與一個普通的實例變量完全是兩碼事融虽。
};
category動態(tài)擴展了原來類的方法享完,在調用者看來好像原來類本來就有這些方法似的,不論有沒有import category 的.h有额,都可以成功調用category的方法般又,都影響不到category的加載流程彼绷,import只是幫助了編譯檢查和鏈接過程。runtime加載完成后茴迁,category的原始信息在類結構里將不會存在寄悯。
objc runtime的加載入口是一個叫_objc_init的方法,在library加載前由libSystem dyld調用堕义,進行初始化操作猜旬。調用map_images方法將文件中的image map到內存。調用_read_images方法初始化map后的image倦卖,這里面干了很多的事情洒擦,像load所有的類、協(xié)議和category糖耸,著名的+ load方法就是這一步調用的秘遏。category的初始化丘薛,循環(huán)調用了_getObjc2CategoryList方法嘉竟。
在調用完_getObjc2CategoryList后,runtime終于開始了category的處理洋侨,首先分成兩撥舍扰,一撥是實例對象相關的調用addUnattachedCategoryForClass,一撥是類對象相關的調用addUnattachedCategoryForClass希坚,然后會調到attachCategoryMethods方法边苹,這個方法把一個類所有的category_list的所有方法取出來組成一個method_list_t ,這里是倒序添加的裁僧,也就是說个束,新生成的category的方法會先于舊的category的方法插入。
生成了所有method的list之后聊疲,調用attachMethodLists將所有方法前序添加進類的方法的數(shù)組中茬底,也就是說,如果原來類的方法是a,b,c获洲,類別的方法是1,2,3阱表,那么插入之后的方法將會是1,2,3,a,b,c,也就是說贡珊,原來類的方法被category的方法覆蓋了最爬,但被覆蓋的方法確實還在那里。
static void attachCategoryMethods(class_t *cls, category_list *cats,
BOOL *inoutVtablesAffected)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
BOOL isMeta = isMetaClass(cls);
method_list_t **mlists = (method_list_t **)
_malloc_internal(cats->count * sizeof(*mlists));
// Count backwards through cats to get newest categories first
int mcount = 0;
int i = cats->count;
BOOL fromBundle = NO;
while (i--) {
method_list_t *mlist = cat_method_list(cats->list[i].cat, isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= cats->list[i].fromBundle;
}
}
attachMethodLists(cls, mlists, mcount, NO, fromBundle, inoutVtablesAffected);
_free_internal(mlists);
}
這也即是我們上面說的Category修復Bug的原理门岔。
Extension
Extension非常像是沒有命名的類別爱致。擴展只是用來定義類的私有方法的,實現(xiàn)要在原始的.m里面寒随。還以用來改變原始屬性的一些性質糠悯。一般的時候胯努,Extension都是放在.m文件中@implementation的上方。 Extension中的方法必須在@implementation中實現(xiàn)逢防,否則編譯會報錯叶沛。Category沒有源代碼的類添加方法,格式:定義一對.h和.m忘朝。Extension作用于管理類的所有方法灰署,格式:把代碼寫到原始類的.m文件中。