一竹握、Demo展示
創(chuàng)建一個(gè)Person
類啦辐,在創(chuàng)建一個(gè)Person+eat
和Person+test
兩個(gè)分類。
@implementation Person
- (void)run {
NSLog(@"run");
}
@end
@implementation Person (test)
- (void)test {
NSLog(@"test");
}
@end
@implementation Person (eat)
- (void)eat {
NSLog(@"eat");
}
@end
// 運(yùn)行下面代碼
Person *person = [[Person alloc] init];
[person run];
[person test];
[person eat];
當(dāng)然上面代碼续挟,會(huì)打印出”run“/"test"/"eat"
我們知道當(dāng)我們調(diào)用一個(gè)方法是侥衬,底層會(huì)調(diào)用objc_msgSend(person, @selector(xxx))
這個(gè)方法轴总,根據(jù)OC對(duì)象的本質(zhì)得知怀樟,具體的實(shí)現(xiàn)是person 的isa 找到類對(duì)象里面的實(shí)例方法,如果是類方法脖含,則會(huì)去元類對(duì)象找類方法。
思考
如上 Demo里面的兩個(gè)分類會(huì)生成兩個(gè)新的類嗎征堪?
不會(huì),一個(gè)isa只會(huì)有一個(gè)類對(duì)象庸娱,程序會(huì)通過runtime動(dòng)態(tài)將實(shí)例方法合并到類對(duì)象里面的對(duì)象方法
中熟尉,類方法都會(huì)合并到元類對(duì)象的類方法
中
二斤儿、Category 內(nèi)部實(shí)現(xiàn)
(一)demo1
使用clang編譯器把OC代碼轉(zhuǎn)成C++恐锦,在終端上cd到當(dāng)前項(xiàng)目的目錄一铅,輸入xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person+eat.m
會(huì)自動(dòng)生成 .cpp 文件
打開上面生成的Person+eat.cpp
文件潘飘,我們會(huì)看到下面代碼
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
static struct _category_t _OBJC_$_CATEGORY_Person_$_eat __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"Person", // 給了我們上面的name
0, // &OBJC_CLASS_$_Person,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_eat, // 就是我們的實(shí)例方法 instance_methods
0,
0,
0,
};
// _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_eat
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"eat", "v16@0:8", (void *)_I_Person_eat_eat}}
};
(二)demo2
在Person+test.h
類中添加
@property (assign, nonatomic) int weight;
@property (assign, nonatomic) double height;
在Person+test.m
類中添加
+ (void)test {
NSLog(@"+test");
}
- (void)test1
{
NSLog(@"eat1");
}
+ (void)test2
{
}
+ (void)test3
{
}
然后同樣運(yùn)行上面 clang 代碼xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person+test.m
生成.cpp
文件
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_Person_$_test __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"Person",
0, // &OBJC_CLASS_$_Person,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_test,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_test,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_test,
};
// 實(shí)例戈擒,_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_test
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[2];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
2,
{{(struct objc_selector *)"test", "v16@0:8", (void *)_I_Person_test_test},
{(struct objc_selector *)"test1", "v16@0:8", (void *)_I_Person_test_test1}}
};
// 類方法 _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_test
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[3];
} _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
3,
{{(struct objc_selector *)"test", "v16@0:8", (void *)_C_Person_test_test},
{(struct objc_selector *)"test2", "v16@0:8", (void *)_C_Person_test_test2},
{(struct objc_selector *)"test3", "v16@0:8", (void *)_C_Person_test_test3}}
};
// 屬性列表 _OBJC_$_PROP_LIST_Person_$_test
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[2];
} _OBJC_$_PROP_LIST_Person_$_test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
2,
{{"weight","Ti,N"},
{"height","Td,N"}}
};
也就是說赘来,我們寫的分類都會(huì)變成_category_t
這種結(jié)構(gòu)體犬辰,在合適的時(shí)機(jī)合并到類的對(duì)象方法或元類的類方法中冰单。
(三)Category 的加載處理過程
- 通過 Runtime 加載某個(gè)類的所有Category 數(shù)據(jù)
2.把所有 Category 的方法诫欠、屬性、協(xié)議數(shù)據(jù)轿偎、合并到一個(gè)大數(shù)組中
- 后面參與編譯的 Category 數(shù)據(jù)坏晦,會(huì)在數(shù)組的前面
3.將合并后的分類數(shù)據(jù)(方法嫁乘、屬性、協(xié)議)仓蛆,插入到類原來數(shù)據(jù)的前面
所以看疙,我們?cè)陂_發(fā)過程中直奋,如果分類和類中的方法名字相同帮碰,會(huì)調(diào)用分類里面的。
三丰涉、load函數(shù)在Category 中的加載
(一)demo
// Person
@implementation Person
+ (void)run {
NSLog(@"Person +run");
}
+ (void)load {
NSLog(@"Person +load");
}
@end
// Person+test
@implementation Person (test)
+ (void)load {
NSLog(@"Person (test) +load");
}
+ (void)test {
NSLog(@"Person (test) +test");
}
@end
// Person+eat
@implementation Person (eat)
+ (void)load {
NSLog(@"Person (eat) +load");
}
+ (void)eat {
NSLog(@"Person (eat) +eat");
}
@end
// 調(diào)用 Person 的 +run方法
[Person run];
我們運(yùn)行程序發(fā)現(xiàn)
2020-06-11 23:18:48.786224+0800 TestDemo[18522:2035515] Person +load
2020-06-11 23:18:48.786723+0800 TestDemo[18522:2035515] Person (test) +load
2020-06-11 23:18:48.786785+0800 TestDemo[18522:2035515] Person (eat) +load
2020-06-11 23:18:48.786926+0800 TestDemo[18522:2035515] Person (eat) +run
思考
在上面的 Category 內(nèi)部實(shí)現(xiàn)證明了一死, 如果該分類的方法和該類的方法名一樣投慈,會(huì)優(yōu)先調(diào)用分類的方法,類里面的方法不會(huì)被調(diào)用加袋。為什么 load 里面的方法都會(huì)被調(diào)用呢职烧,而不是像 run 方法一樣蚀之?
load 方法的調(diào)用捷泞,是因?yàn)樵诔绦蚣虞d過程中锁右,如果發(fā)現(xiàn)是分類骡湖,會(huì)直接指向分類的類方法列表响蕴,而不是去調(diào)用的組合后的方法列表浦夷。所以會(huì)調(diào)用三次辜王。
+load方法是根據(jù)方法地址直接調(diào)用呐馆,并不是經(jīng)過objc_msgSend函數(shù)調(diào)用
test 方法的調(diào)用汹来,我們知道方法的調(diào)用實(shí)際就是調(diào)用
objc_megSend([Person class] @selector(test))
會(huì)通過isa找到當(dāng)前對(duì)應(yīng)的類對(duì)象或元類對(duì)象改艇,調(diào)用里面的方法列表坟岔。因?yàn)橹匦陆M裝的方法列表社付,Person+eat 分類在最前面鸥咖,所以會(huì)調(diào)用 Person+eat 這個(gè)類中的 run 方法+load方法會(huì)在 Runtime 加載類扛或、分類時(shí)間調(diào)用熙兔,并且每個(gè)類住涉、分類的 +load 在程序運(yùn)行過程中只會(huì)調(diào)用一次舆声。
(二)load 調(diào)用順序
1、先調(diào)用類
的 +load 方法
- 按照編譯先后順序調(diào)用(先編譯蛾找,先調(diào)用)
- 調(diào)用子類的 +load 之前會(huì)先調(diào)用父類的 +load
2打毛、在調(diào)用分類
的 +load 方法
- 按照編譯先后順序調(diào)用(先編譯柿赊,先調(diào)用)
擴(kuò)展
打印出某個(gè)類中的所有方法
- (void)printMethodNamesOfClass:(Class )cls {
unsigned int count;
// 獲取方法數(shù)組
Method *methodList = class_copyMethodList(cls, &count);
// 存儲(chǔ)方法名
NSMutableString *methodNames = [NSMutableString string];
// 遍歷所有的方法
for (int i = 0; i < count; i++) {
// 獲得方法
Method method = methodList[i];
// 獲得方法名
NSString *methodName = NSStringFromSelector(method_getName(method));
// 拼接方法名
[methodNames appendString:methodName];
[methodNames appendString:@", "];
}
// 釋放
free(methodList);
// 打印方法名
NSLog(@"%@, %@", cls, methodNames);
}
問題
1、給一個(gè)存在的類添加兩個(gè)分類幻枉,會(huì)生成兩個(gè)新的類嗎碰声?
不會(huì),一個(gè)isa只會(huì)有一個(gè)類對(duì)象熬甫,程序會(huì)通過runtime動(dòng)態(tài)將實(shí)例方法合并到類對(duì)象里面的對(duì)象方法
中胰挑,類方法都會(huì)合并到元類對(duì)象的類方法
中。
2、Category 的使用場(chǎng)合
- 給一個(gè)類添加新的方法洽腺,可以為系統(tǒng)的類擴(kuò)展功能脚粟。
- 分解體積龐大的類文件,可以將一個(gè)類按照功能拆解成多個(gè)模塊蘸朋,方便代碼管理核无。
- 創(chuàng)建對(duì)私有方法的前向引用:聲明私有方法团南,把Framework的私有方法公開等,直接調(diào)用其他類的私有方法時(shí)編譯器會(huì)報(bào)錯(cuò)拷橘,這時(shí)候可以創(chuàng)建一個(gè)該類的分類,在分類中聲明這些私有方法(不必提供方法實(shí)現(xiàn)),接著導(dǎo)入這個(gè)分類的頭文件就可以正常調(diào)用這些私有方法。
- 向?qū)ο筇砑臃钦絽f(xié)議:創(chuàng)建一個(gè) NSObject 或其子類的分類稱為 “創(chuàng)建一個(gè)非正式協(xié)議”。
正式協(xié)議是通過 protocol 指定的一系列方法的聲明没龙,然后由遵守該協(xié)議的類自己去實(shí)現(xiàn)這些方法碘梢。而非正式協(xié)議是通過給 NSObject 或其子類添加一個(gè)分類來實(shí)現(xiàn)。非正式協(xié)議已經(jīng)漸漸被正式協(xié)議取代在扰,正式協(xié)議最大的優(yōu)點(diǎn)就是可以使用泛型約束,而非正式協(xié)議不可以。)
3娜汁、Category中都可以添加哪些內(nèi)容
- 實(shí)例方法傅事、類方法、協(xié)議般又、屬性(只生成setter和getter方法的聲明萤衰,不會(huì)生成setter和getter方法的實(shí)現(xiàn)以及下劃線成員變量)倦卖。
- 默認(rèn)情況下,因?yàn)榉诸惖讓咏Y(jié)構(gòu)的限制掸茅,不能添加成員變量到分類中,但可以通過關(guān)聯(lián)對(duì)象來間接實(shí)現(xiàn)這種效果。
4、Category的優(yōu)缺點(diǎn)沪悲、特點(diǎn)涉馁、注意點(diǎn)
Category | 描述 |
---|---|
優(yōu)點(diǎn) | 1糠悯、使用場(chǎng)合, 2、可以按照需求加載不同的類阅悍。 |
缺點(diǎn) | 1假栓、不能直接添加成員變量,可以通過關(guān)聯(lián)對(duì)象實(shí)現(xiàn)這種效果, 2贞滨、分類方法會(huì)“覆蓋”同名的宿主類方法骄噪,如果使用不當(dāng)會(huì)造成問題 |
特點(diǎn) | 1、運(yùn)行時(shí)決議, 2掌实、可以有聲明忱嘹、可以有實(shí)現(xiàn)础米。 3蘑斧、可以為系統(tǒng)的類添加分類花颗, 運(yùn)行時(shí)決議:Category 編譯之后的底層結(jié)構(gòu)是struct category_t,里面存儲(chǔ)著分類的對(duì)象方法、類方法、屬性、協(xié)議信息搀矫,這時(shí)候分類中的數(shù)據(jù)還沒有合并到類中噪馏,而是在程序運(yùn)行的時(shí)候通過Runtime機(jī)制將所有分類數(shù)據(jù)合并到類(類對(duì)象、元類對(duì)象)中去桃移。(這是分類最大的特點(diǎn)疮装,也是分類和擴(kuò)展的最大區(qū)別呻纹,擴(kuò)展是在編譯的時(shí)候就將所有數(shù)據(jù)都合并到類中去了) |
注意點(diǎn) | 1、分類方法會(huì)“覆蓋”同名的宿主類方法渔工,如果使用不當(dāng)會(huì)造成問題兰吟; 2遵湖、同名分類方法誰能生效取決于編譯順序,最后參與編譯的分類中的同名方法會(huì)最終生效; 3赦颇、名字相同的分類會(huì)引起編譯報(bào)錯(cuò)沪摄。 |
5、Category 的實(shí)現(xiàn)原理
- 分類的實(shí)現(xiàn)原理取決于運(yùn)行時(shí)決議屋吨;
- 同名分類方法誰能生效取決于編譯順序直秆,最后參與編譯的分類中的同名方法會(huì)最終生效歇竟;
- 分類方法會(huì)“覆蓋”同名的宿主類(原類)方法,這里說的“覆蓋”并不是指原來的方法沒了梯醒。消息傳遞過程中優(yōu)先查找宿主類中靠前的元素籽慢,找到同名方法就進(jìn)行調(diào)用箱亿,但實(shí)際上宿主類中原有同名方法的實(shí)現(xiàn)仍然是存在的脑豹。我們可以通過一些手段來調(diào)用到宿主類原有同名方法的實(shí)現(xiàn)击碗,如可以通過Runtime的class_copyMethodList方法打印類的方法列表甲馋,找到宿主類方法的imp,進(jìn)行調(diào)用(可以交換方法實(shí)現(xiàn))液茎。
6逞姿、Category的加載處理過程
在編譯時(shí),Category 中的數(shù)據(jù)還沒有合并到類中捆等,而是在程序運(yùn)行的時(shí)候通過Runtime機(jī)制將所有分類數(shù)據(jù)合并到類(類對(duì)象滞造、元類對(duì)象)中去。下面我們來看一下 Category 的加載處理過程栋烤。
① 通過Runtime加載某個(gè)類的所有 Category 數(shù)據(jù)断部;
② 把所有的分類數(shù)據(jù)(方法、屬性班缎、協(xié)議)蝴光,合并到一個(gè)大數(shù)組中;(后面參與編譯的 Category 數(shù)據(jù)达址,會(huì)在數(shù)組的前面)
③ 將合并后的分類數(shù)據(jù)(方法蔑祟、屬性、協(xié)議)沉唠,插入到宿主類原來數(shù)據(jù)的前面疆虚。(所以會(huì)優(yōu)先調(diào)用最后參與編譯的分類中的同名方法)