iOS 分類(Category)

一瘟栖、分類的使用場(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

attachLists函數(shù)內(nèi)部實(shí)現(xiàn)

上述源代碼中有兩個(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)過內(nèi)存移動(dòng)和拷貝時(shí)

經(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]));

memmove方法之后內(nèi)存變化

經(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]));

memmove方法之后,內(nèi)存變化

我們發(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)存中的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末辅鲸,一起剝皮案震驚了整個(gè)濱河市格郁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖例书,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锣尉,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡决采,警方通過查閱死者的電腦和手機(jī)自沧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來树瞭,“玉大人拇厢,你說我怎么就攤上這事∩古纾” “怎么了孝偎?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)凉敲。 經(jīng)常有香客問我邪媳,道長(zhǎng),這世上最難降的妖魔是什么荡陷? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮迅涮,結(jié)果婚禮上废赞,老公的妹妹穿的比我還像新娘。我一直安慰自己叮姑,他們只是感情好唉地,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著传透,像睡著了一般耘沼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上朱盐,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天群嗤,我揣著相機(jī)與錄音,去河邊找鬼兵琳。 笑死狂秘,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的躯肌。 我是一名探鬼主播者春,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼清女!你這毒婦竟也來了钱烟?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拴袭,沒想到半個(gè)月后读第,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡稻扬,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年卦方,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泰佳。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡盼砍,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出逝她,到底是詐尸還是另有隱情浇坐,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布黔宛,位于F島的核電站近刘,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏臀晃。R本人自食惡果不足惜觉渴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望徽惋。 院中可真熱鬧案淋,春花似錦、人聲如沸险绘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)宦棺。三九已至瓣距,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間代咸,已是汗流浹背蹈丸。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留呐芥,地道東北人白华。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像贩耐,于是被迫代替她去往敵國(guó)和親弧腥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 1.Category是什么潮太?重寫一個(gè)類的方法用繼承好還是分類好管搪? Category是類別虾攻;一般情況用分類好,用C...
    齊玉婷閱讀 2,318評(píng)論 0 3
  • 1更鲁,分類可以干什么:聲明私有方法霎箍,分解體積龐大的類文件,把Framework的私有方法公開 2澡为,特點(diǎn):運(yùn)行時(shí)決議(...
    pengmengli閱讀 974評(píng)論 0 0
  • 1. 分類是什么 分類是利用 OC 的動(dòng)態(tài)運(yùn)行時(shí)機(jī)制漂坏,實(shí)現(xiàn)對(duì)一個(gè)現(xiàn)有類的功能進(jìn)行擴(kuò)展而不必知道該類的源碼。所以分類...
    逐日追星看月亮閱讀 261評(píng)論 0 1
  • 第一部分:有關(guān)分類的本質(zhì)媒至、原理Q:分類的對(duì)象方法顶别,類方法都存在哪里?一個(gè)類的所有分類的 對(duì)象方法放在類對(duì)象中拒啰,所有...
    薩繆閱讀 412評(píng)論 1 4
  • 主要講解 category 的基礎(chǔ)知識(shí); isa和 superclass 的指向關(guān)系[https://www.ji...
    飛不越瘋?cè)嗽?/span>閱讀 1,389評(píng)論 0 3