一瘟栖、分類的使用場(chǎng)景
- 可以減少單個(gè)文件的體積
- 可以按照功能分組,放到不同的分類里憾朴,使類結(jié)構(gòu)更清晰
- 降低耦合性贫贝,同一個(gè)類可以有多個(gè)開發(fā)人員進(jìn)行開發(fā)
- 模擬多繼承
- 把靜態(tài)庫(kù)的私有方法公開
二、特點(diǎn)
- 運(yùn)行時(shí)決議
- 給系統(tǒng)類添加分類
三殖氏、分類的底層結(jié)構(gòu)
在runtime
文件objc-runtime-new.h
中晚树,找到分類category_t
的結(jié)構(gòu)體:
struct category_t {
const char *name; //分類的名稱
classref_t cls; //類
struct method_list_t *instanceMethods; //分類中所有給類添加的實(shí)例方法的列表
struct method_list_t *classMethods; //分類中所有給類添加的類方法的列表
struct protocol_list_t *protocols; //實(shí)現(xiàn)的所有協(xié)議的列表
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);
protocol_list_t *protocolsForMeta(bool isMeta) {
if (isMeta) return nullptr;
else return protocols;
}
};
從上面的category_t
結(jié)構(gòu)體中可以看出,我們可以在分類中添加實(shí)例方法
雅采、類方法
爵憎、協(xié)議
、屬性
婚瓜。
并且我們發(fā)現(xiàn)分類結(jié)構(gòu)體中不存在成員變量的宝鼓,因此分類是不允許添加成員變量的。分類中添加的屬性并不會(huì)幫組我們自動(dòng)生成成員變量巴刻,只會(huì)生成get set
方法的聲明愚铡,需要我們自己去實(shí)現(xiàn)。
下面寫一個(gè)簡(jiǎn)單的分類胡陪,然后將其轉(zhuǎn)換為c/c++的 .cpp
文件沥寥。
/** Person.h */
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *age;
- (void)run;
@end
/** Person+Test.h */
@interface Person (Test)<NSCopying>
@property (nonatomic, copy) NSString *categoryName;
- (void)printClassName;
+ (void)classMethods;
@end
/** Person+Test.m */
@implementation Person (Test)
- (void)setCategoryName:(NSString *)categoryName {
}
- (NSString *)categoryName {
return @"categoryName";
}
- (void)printClassName {
NSLog(@"printClassName");
}
+ (void)classMethods {
NSLog(@"classMethods");
}
#pragma mark -- NSCopying
- (id)copyWithZone:(nullable NSZone *)zone {
return self;
}
@end
使用clang
命令生成.cpp文件
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Preson+Test.m
1、_category_t
在分類轉(zhuǎn)化為c++文件中可以看出_category_t
結(jié)構(gòu)體中柠座,存放著類名
邑雅,對(duì)象方法列表
,類方法列表
愚隧,協(xié)議列表
蒂阱,以及屬性列表
。
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;
};
2狂塘、_method_list_t
然后是_method_list_t
類型結(jié)構(gòu)體
對(duì)象方法
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[3];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
3,
{{(struct objc_selector *)"setCategoryName:", "v24@0:8@16", (void *)_I_Person_Test_setCategoryName_},
{(struct objc_selector *)"categoryName", "@16@0:8", (void *)_I_Person_Test_categoryName},
{(struct objc_selector *)"printClassName", "v16@0:8", (void *)_I_Person_Test_printClassName}}
};
上圖中我們發(fā)現(xiàn)這個(gè)結(jié)構(gòu)體_OBJC_$_CATEGORY_INSTANCE_METHODS_Preson_$_Test
從名稱可以看出是INSTANCE_METHODS
對(duì)象方法录煤,并且一一對(duì)應(yīng)為上面結(jié)構(gòu)體內(nèi)賦值。我們可以看到結(jié)構(gòu)體中存儲(chǔ)了方法占用的內(nèi)存
荞胡,方法數(shù)量
妈踊,以及方法列表
。并且從上圖中找到分類中我們實(shí)現(xiàn)對(duì)應(yīng)的對(duì)象方法泪漂,setCategoryName
, categoryName
, printClassName
三個(gè)方法廊营。
類方法
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_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"classMethods", "v16@0:8", (void *)_C_Person_Test_classMethods}}
};
同上面對(duì)象方法列表一樣歪泳,這個(gè)我們可以看出是類方法列表結(jié)構(gòu)體 _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Test
,同對(duì)象方法結(jié)構(gòu)體相同露筒,同樣可以看到我們實(shí)現(xiàn)的類方法呐伞,classMethods
。
3慎式、_protocol_list_t
協(xié)議列表
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_PROTOCOL_INSTANCE_METHODS_NSCopying __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"copyWithZone:", "@24@0:8^{_NSZone=}16", 0}}
};
struct _protocol_t _OBJC_PROTOCOL_NSCopying __attribute__ ((used)) = {
0,
"NSCopying",
0,
(const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_NSCopying,
0,
0,
0,
0,
sizeof(_protocol_t),
0,
(const char **)&_OBJC_PROTOCOL_METHOD_TYPES_NSCopying
};
struct _protocol_t *_OBJC_LABEL_PROTOCOL_$_NSCopying = &_OBJC_PROTOCOL_NSCopying;
static struct /*_protocol_list_t*/ {
long protocol_count; // Note, this is 32/64 bit
struct _protocol_t *super_protocols[1];
} _OBJC_CATEGORY_PROTOCOLS_$_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
1,
&_OBJC_PROTOCOL_NSCopying
};
通過上述源碼可以看到先將協(xié)議方法通過_method_list_t
結(jié)構(gòu)體存儲(chǔ)伶氢,之后通過_protocol_t
結(jié)構(gòu)體存儲(chǔ)在_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Test
中同_protocol_list_t
結(jié)構(gòu)體一一對(duì)應(yīng),分別為protocol_count
協(xié)議數(shù)量以及存儲(chǔ)了協(xié)議方法的_protocol_t
結(jié)構(gòu)體瘪吏。
4癣防、_prop_list_t
屬性列表
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{{"categoryName","T@\"NSString\",C,N"}}
};
屬性列表結(jié)構(gòu)體_OBJC_$_PROP_LIST_Person_$_Test
同_prop_list_t
結(jié)構(gòu)體對(duì)應(yīng),存儲(chǔ)屬性的占用空間掌眠,屬性屬性數(shù)量蕾盯,以及屬性列表,從上圖中可以看到我們自己寫的categoryName
屬性蓝丙。
5级遭、賦值
最后我們可以看到定義了_OBJC_$_CATEGORY_Preson_$_Test
結(jié)構(gòu)體,并且將我們上面著重分析的結(jié)構(gòu)體一一賦值迅腔,我們通過兩段代碼對(duì)照一下装畅。
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,
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Person_$_Test,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_Test,
};
static void OBJC_CATEGORY_SETUP_$_Person_$_Test(void ) {
_OBJC_$_CATEGORY_Person_$_Test.cls = &OBJC_CLASS_$_Person;
}
上下兩張圖一一對(duì)應(yīng),并且我們看到定義_class_t
類型的OBJC_CLASS_$_Preson
結(jié)構(gòu)體沧烈,最后將_OBJC_$_CATEGORY_Preson_$_Test
的cls指針指向OBJC_CLASS_$_Preson
結(jié)構(gòu)體地址掠兄。我們這里可以看出,cls指針指向的應(yīng)該是分類的主類類對(duì)象的地址锌雀。
分類的實(shí)現(xiàn)原理是將category中的方法蚂夕,屬性,協(xié)議數(shù)據(jù)放在category_t結(jié)構(gòu)體中腋逆,然后將結(jié)構(gòu)體內(nèi)的
方法列表
拷貝到類對(duì)象
的方法列表
中婿牍。
Category可以添加屬性,但是并不會(huì)自動(dòng)生成成員變量及set/get方法惩歉。因?yàn)閏ategory_t結(jié)構(gòu)體中并不存在成員變量等脂。通過之前對(duì)對(duì)象的分析我們知道成員變量是存放在實(shí)例對(duì)象中的,并且編譯的那一刻就已經(jīng)決定好了撑蚌。而分類是在運(yùn)行時(shí)才去加載的上遥。那么我們就無法再程序運(yùn)行時(shí)將分類的成員變量中添加到實(shí)例對(duì)象的結(jié)構(gòu)體中。因此分類中不可以添加成員變量争涌。
四粉楚、catagory_t的方法,屬性,協(xié)議等的存儲(chǔ)類對(duì)象中
objc4-781.tar.gz
1模软、runtime初始化函數(shù)
2伟骨、map_images
接著我們來到 &map_images讀取模塊(images這里代表模塊或鏡像)
來到map_images_nolock函數(shù)中找到_read_images函數(shù),在_read_images函數(shù)中我們找到load_categories_nolock函數(shù)
從上述代碼中我們可以知道這段代碼是用來查找有沒有分類的燃异。通過_getObjc2CategoryList函數(shù)獲取到分類列表之后携狭,進(jìn)行遍歷,獲取其中的方法回俐,協(xié)議暑中,屬性等■杲耍可以看到最終都調(diào)用了attachCategories();函數(shù)。我們來到attachCategories();函數(shù)內(nèi)部查看稻轨。
3灵莲、attachCategories();
上述源碼中可以看出,首先根據(jù)方法列表殴俱,屬性列表政冻,協(xié)議列表,malloc分配內(nèi)存线欲,根據(jù)多少個(gè)分類以及每一塊方法需要多少內(nèi)存來分配相應(yīng)的內(nèi)存地址明场。之后從分類數(shù)組里面往三個(gè)數(shù)組里面存放分類數(shù)組里面存放的分類方法,屬性以及協(xié)議放入對(duì)應(yīng)mlist李丰、proplists苦锨、protolosts數(shù)組中,這三個(gè)數(shù)組放著所有分類的方法趴泌,屬性和協(xié)議舟舒。
之后分別通過rwe調(diào)用方法列表、屬性列表嗜憔、協(xié)議列表的attachList函數(shù)秃励,將所有的分類的方法、屬性吉捶、協(xié)議列表數(shù)組傳進(jìn)去夺鲜,我們大致可以猜想到在attachList方法內(nèi)部將分類和本類相應(yīng)的對(duì)象方法,屬性呐舔,和協(xié)議進(jìn)行了合并币励。
4、attachLists
上述源代碼中有兩個(gè)重要的數(shù)組
array()->lists: 類對(duì)象原來的方法列表滋早,屬性列表榄审,協(xié)議列表。
addedLists:傳入所有分類的方法列表杆麸,屬性列表搁进,協(xié)議列表浪感。
attachLists函數(shù)中最重要的兩個(gè)方法為memmove內(nèi)存移動(dòng)和memcpy內(nèi)存拷貝。我們先來分別看一下這兩個(gè)函數(shù)
// memmove :內(nèi)存移動(dòng)饼问。
/* __dst : 移動(dòng)內(nèi)存的目的地
* __src : 被移動(dòng)的內(nèi)存首地址
* __len : 被移動(dòng)的內(nèi)存長(zhǎng)度
* 將__src的內(nèi)存移動(dòng)__len塊內(nèi)存到__dst中
*/
void *memmove(void *__dst, const void *__src, size_t __len);
// memcpy :內(nèi)存拷貝影兽。
/* __dst : 拷貝內(nèi)存的拷貝目的地
* __src : 被拷貝的內(nèi)存首地址
* __n : 被移動(dòng)的內(nèi)存長(zhǎng)度
* 將__src的內(nèi)存移動(dòng)__n塊內(nèi)存到__dst中
*/
void *memcpy(void *__dst, const void *__src, size_t __n);
下面我們圖示經(jīng)過memmove和memcpy方法過后的內(nèi)存變化。
經(jīng)過memmove方法之后莱革,內(nèi)存變化為
// array()->lists 原來方法峻堰、屬性、協(xié)議列表數(shù)組
// addedCount 分類數(shù)組長(zhǎng)度
// oldCount * sizeof(array()->lists[0]) 原來數(shù)組占據(jù)的空間
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
經(jīng)過memmove方法之后盅视,我們發(fā)現(xiàn)捐名,雖然本類的方法,屬性闹击,協(xié)議列表會(huì)分別后移镶蹋,但是本類的對(duì)應(yīng)數(shù)組的指針依然指向原始位置。
memcpy方法之后赏半,內(nèi)存變化
// array()->lists 原來方法贺归、屬性、協(xié)議列表數(shù)組
// addedLists 分類方法断箫、屬性拂酣、協(xié)議列表數(shù)組
// addedCount * sizeof(array()->lists[0]) 原來數(shù)組占據(jù)的空間
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
我們發(fā)現(xiàn)原來指針并沒有改變仲义,至始至終指向開頭的位置婶熬。并且經(jīng)過memmove和memcpy方法之后,分類的方法光坝,屬性尸诽,協(xié)議列表被放在了類對(duì)象中原本存儲(chǔ)的方法,屬性盯另,協(xié)議列表前面性含。
那么為什么要將分類方法的列表追加到本來的對(duì)象方法前面呢,這樣做的目的是為了保證分類方法優(yōu)先調(diào)用鸳惯,我們知道當(dāng)分類重寫本類的方法時(shí)商蕴,會(huì)覆蓋本類的方法。
其實(shí)經(jīng)過上面的分析我們知道本質(zhì)上并不是覆蓋芝发,而是優(yōu)先調(diào)用绪商。本類的方法依然在內(nèi)存中的。