分類category
category
的介紹
??Category
是oc2.0添加的語言特性,它的主要作用是在不改變本類下茂附,動態(tài)地給這個類添加方法,除此之外Category
還可以將一個類進行拆分在不同文件下進行管理、甚至可以模擬實現(xiàn)多繼承。若Category
添加的方法在基類中已經(jīng)存在手报,則會‘覆蓋’基類的同名方法,這里的覆蓋并不是真正的覆蓋改化,而是在方法列表中取用了第一個同名方法名掩蛤。
分類、類陈肛、父類中實例方法和類方法調(diào)用順序
??分類揍鸟、類和父類的方法調(diào)用順序,在我另外一片關(guān)于iOS底層實現(xiàn)的文章中句旱,分析了類的本質(zhì)和方法存放位置阳藻,不清楚的同學(xué)可以先看下這篇文章,通過之前的分析我們知道方法都是將方法轉(zhuǎn)換成objc_msgSeng(obj,SEL)
的形式進行消息發(fā)送的前翎,首先會通過查找obj
的isa找到其類對象稚配,然后在類對象的方法列表中查找SEL
方法,如果能找到的話就調(diào)用類對象的方法港华,如果類方法中不存在該方法就通過supclass指針找到其父類對象,在它父類對象的方法列表中查找該方法午衰,這樣一層層的查找實例方法立宜,類似的類方法就是去其元類對象找那個查找而已冒萄,流程是一樣的。
??知道了類和父類的方法調(diào)用順序后橙数,我們就好奇category
中的方法是怎么調(diào)用的尊流,它的結(jié)構(gòu)體是怎樣的。這里我先給大家打印下category
在c語言中的結(jié)構(gòu)體以及項目中寫的真是分類對應(yīng)取值
struct _category_t {
const char *name;
struct _class_t *cls;
const struct _method_list_t *instance_methods;
const struct _method_list_t *class_methods;
const struct _protocol_list_t *protocols;
const struct _prop_list_t *properties;
};
static struct _category_t _OBJC_$_CATEGORY_PQStudent_$_test __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"PQStudent",
0, // &OBJC_CLASS_$_PQStudent,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_PQStudent_$_test,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_PQStudent_$_test,
0,
0,
};
分析結(jié)構(gòu)體灯帮,不難發(fā)現(xiàn)崖技,結(jié)構(gòu)體中有六個字段組成,分別是本類名稱钟哥、本壘的類機構(gòu)體迎献、實例方法列表、類方法列表腻贰、協(xié)議列表吁恍、屬性列表,所以分類是不允許添加成員變量的播演,要想添加屬性冀瓦,必須通過添加關(guān)聯(lián)實現(xiàn)setter和getter方法,然后在加載分類的時候写烤,系統(tǒng)會遍歷分類列表將各個分類的實例方法和類方法插入到類方法列表中和元類方法列表中翼闽,并且會根據(jù)先后編譯順序,以此排列洲炊,所以如果存在同名函數(shù)并不是覆蓋感局,而是取用的時候有先后順序⊙』耄總結(jié)分類蓝厌、類、父類的調(diào)用順序是古徒,先調(diào)用分類中的方法拓提,然后調(diào)用類中的方法最后調(diào)用父類中的方法,分類如果有同名的方法隧膘,后編譯的分類中方法先調(diào)用代态。
??在iOS開發(fā)過程中,分類category可以用來擴展一個類的功能疹吃,讓這個本類功能更強大蹦疑,而使用分類的方式進行管理,在一定前提下會比使用子類方式更好管理萨驶,這個前提是在分類以及其下面的方法命名要規(guī)范歉摧,另外分類不能直接添加成員屬性,如果一定要添加的話可以使用objc_getAssociatedObject
和objc_setAssociatedObject
方法綁定get和set方法,這里就不講這兩個方法的使用了叁温。
分類再悼、子類、父類的加載順序
oc在編譯的時候膝但,就會加載所有的類冲九,加載的順序依次是父類->子類->分類,在加載完類后會調(diào)用load方法跟束,我們可以在load方法中打印驗證加載順序莺奸,這里簡單講訴下,load
方法的調(diào)用原理冀宴,load
方法不同于其他類方法或者實例方法是通過消息機制objc_msgSend
進行調(diào)用的灭贷,實際上load
方法是直接調(diào)用load
所造內(nèi)存指針指向的方法,看源碼可以發(fā)現(xiàn)花鹅,load
方法的調(diào)用是遞歸進行的氧腰,在調(diào)用自身之前會先調(diào)用父類的load方法。
2019-02-27 16:36:44.842874+0800 swizzleDemo[13546:18542400] wenpq...本類加載
2019-02-27 16:36:44.843789+0800 swizzleDemo[13546:18542400] wenpq...子類加載
2019-02-27 16:36:44.844219+0800 swizzleDemo[13546:18542400] wenpq...分類加載
load方法只會加載一次刨肃,并且不同于initialize
這種懶加載方法古拴,所以load是進行swizzle的很好方法。
swizzle使用方法
為了保證swizzle只執(zhí)行一次真友,所以放在load方法中執(zhí)行黄痪,其中originMethod
代表舊方法,newMethod
代表新方法盔然,在方法交換前先用class_addMethod
添加方法桅打,防止newMethod
不存在導(dǎo)致崩潰,class_addMethod
方法在newMethod
存在的情況下愈案,仍會返回NO.
+ (void)load {
NSLog(@"wenpq...分類加載");
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method originMethod = class_getInstanceMethod([self class], @selector(work));
Method newMethod = class_getInstanceMethod([self class], @selector(swizzle_work));
BOOL addSuccess = class_addMethod([self class], @selector(swizzle_work), method_getImplementation(newMethod), method_getTypeEncoding(newMethod));
// 如果方法存在挺尾,也會添加失敗
if (addSuccess) {
//用剛add的新方法replace舊方法
class_replaceMethod([self class], @selector(work), method_getImplementation(originMethod), method_getTypeEncoding(originMethod));
} else {
method_exchangeImplementations(originMethod, newMethod);
}
});
}
- (void)swizzle_work {
NSLog(@"wenpq...分類work");
[self swizzle_work];//調(diào)用原來的方法
}
目前只列出swizzle的是使用方法,之后會補充常用使用場景
項目demo地址