2019再看Category

官網(wǎng)文檔 - Category
Runtime開(kāi)源代碼
美團(tuán)技術(shù) - 深入理解Objective-C:Category
Category的本質(zhì)<一>
結(jié)合 Category 工作原理分析 OC2.0 中的 runtime

本文在github上也有 2019再看Category

一、Category簡(jiǎn)介

1.1哗戈、什么是Category棒口?

Category是Objective-C 2.0之后添加的語(yǔ)言特性尚猿,category的主要作用是為已經(jīng)存在的類添加方法信姓。除此之外阁谆,apple 還推薦了category的另外兩個(gè)使用場(chǎng)景:

  • Distribute the implementation of your own classes into separate source files—for example, you could group the methods of a large class into several categories and put each category in a different file.

  • Declare private methods.

大致意思:

  • 可以把類的實(shí)現(xiàn)分開(kāi)在幾個(gè)不同的文件里面
  • 聲明私有方法

不過(guò)除了apple推薦的使用場(chǎng)景往核,廣大開(kāi)發(fā)者腦洞大開(kāi),還衍生出了category的其他幾個(gè)使用場(chǎng)景:

  • 模擬多繼承
  • 把framework的私有方法公開(kāi)

1.2犁苏、Category好處

可以把類的實(shí)現(xiàn)分開(kāi)在幾個(gè)不同的文件里面硬萍。這樣做有幾個(gè)顯而易見(jiàn)的好處。

  • 可以減少單個(gè)文件的體積
  • 可以把不同的功能組織到不同的category里
  • 可以由多個(gè)開(kāi)發(fā)者共同完成一個(gè)類
  • 可以按需加載想要的category
  • 聲明私有方法

1.3围详、category特點(diǎn)

  • category只能給某個(gè)已有的類擴(kuò)充方法襟铭,不能擴(kuò)充成員變量。
  • category中也可以添加屬性短曾,只不過(guò)@property只會(huì)生成setter和getter的聲明寒砖,不會(huì)生成setter和getter的實(shí)現(xiàn)以及成員變量。
  • 如果category中的方法和類中原有方法同名嫉拐,運(yùn)行時(shí)會(huì)優(yōu)先調(diào)用category中的方法哩都。也就是,category中的方法會(huì)覆蓋掉類中原有的方法婉徘。所以開(kāi)發(fā)中盡量保證不要讓分類中的方法和原有類中的方法名相同漠嵌。避免出現(xiàn)這種情況的解決方案是給分類的方法名統(tǒng)一添加前綴。比如category_盖呼。
  • 如果多個(gè)category中存在同名的方法儒鹿,運(yùn)行時(shí)到底調(diào)用哪個(gè)方法由編譯器決定,最后一個(gè)參與編譯的方法會(huì)被調(diào)用几晤。

1.4约炎、Category應(yīng)用場(chǎng)景

  • 將類的實(shí)現(xiàn)分開(kāi)在幾個(gè)不同的文件中
  • 聲明私有方法
  • 模擬多繼承(另外可以模擬多繼承的還有protocol)
  • 把framework的私有方法公開(kāi)

1.5、調(diào)用優(yōu)先級(jí)

  • 在本類和分類有相同的方法時(shí),優(yōu)先調(diào)用分類的方法再調(diào)用本類的方法(分類 > 本類 > 父類)圾浅。
  • 如果有兩個(gè)分類掠手,他們都實(shí)現(xiàn)了相同的方法(+load方法也算,具體看4.1)狸捕,如何判斷誰(shuí)先執(zhí)行喷鸽?分類執(zhí)行順序可以通過(guò)targets -> Build Phases -> Complie Source進(jìn)行調(diào)節(jié),注意執(zhí)行順序是從上到下的灸拍。(只有兩個(gè)相同方法名的分類)

注意:category是在運(yùn)行時(shí)加載的做祝,不是在編譯時(shí)。

二鸡岗、Category在Runtime層的實(shí)現(xiàn)原理(編譯器的工作)

2.1剖淀、舉個(gè)栗子

Person.h (包含Person類 、協(xié)議纤房、 Person+DD分類)

#import <Foundation/Foundation.h>

@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
+(void)eat;
-(void)run;
@end

//==================================================
@protocol MyProtocol <NSObject>
- (void)requiredMethod;
-(void)optionalMethod;
@end

//==================================================
@interface Person (DD) <MyProtocol>
@property (nonatomic, copy) NSString *height;
@property (nonatomic, strong) NSNumber *weight;
+(void)eat_Category;
-(void)run_Category;
@end

Person.m

#import "Person.h"
#import <objc/runtime.h>

@implementation Person
+(void)eat {
    NSLog(@"本類 類方法:eat");
}
-(void)run {
    NSLog(@"本類 對(duì)象方法:run");
}
@end

//==================================================
@implementation Person (DD)

#pragma mark - setter/getter
-(void)setHeight:(NSString *)height {
    objc_setAssociatedObject(self, @selector(height), height, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)height {
    return objc_getAssociatedObject(self, _cmd);
}
- (void)setWeight:(NSNumber *)weight {
    objc_setAssociatedObject(self, @selector(weight), weight, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSNumber *)weight {
    return objc_getAssociatedObject(self, _cmd);
}

#pragma mark - Method
+(void)eat_Category {
    NSLog(@"分類 類方法:eat_Category");
}

-(void)run_Category {
    NSLog(@"分類 對(duì)象方法:run_Category");
}

#pragma mark - Protocol method
-(void)requiredMethod {
    NSLog(@"分類 Protocol requiredMethod");
}
-(void)optionalMethod {
    NSLog(@"分類 Protocol optionalMethod");
}
@end

調(diào)用

    Person *p = [Person new];
    
    // 本類屬性
    p.name = @"lin";
    p.age = 20;
    // 分類屬性
    p.height = @"60";
    p.weight = [NSNumber numberWithInteger:120];
    
    // 本類方法
    [Person eat];
    [p run];
    
    // 分類新增方法
    [Person eat_Category];
    [p run_Category];
    
    // 分類 協(xié)議方法
    [p requiredMethod];
    [p optionalMethod];

2.2、將Objective-c的代碼轉(zhuǎn)化為c++的源碼

我們知道翻诉,所有的OC類和對(duì)象炮姨,在runtime層都是用struct表示的,category也不例外碰煌,在runtime層舒岸,category用結(jié)構(gòu)體category_t(在objc-runtime-new.h中可以找到此定義),它包含了:

struct category_t {
    const char *name;
    classref_t cls;
    struct method_list_t *instanceMethods;
    struct method_list_t *classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;
    // Fields below this point are not always present on disk. 
    // 譯:此點(diǎn)下方的字段并不總是出現(xiàn)在磁盤(pán)上
    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);
};

我們?cè)诜诸愔行略?1.對(duì)象方法芦圾;2.類方法蛾派;3.協(xié)議;4.屬性

通過(guò)將Objective-c的代碼轉(zhuǎn)化為c++的源碼窺探一下Category的底層結(jié)構(gòu)个少。在命令行輸入clang -rewrite-objc Person.m洪乍,這樣Person.m這個(gè)文件就被轉(zhuǎn)化為了c++的源碼Person.cpp。

  • struct _category_t :該結(jié)構(gòu)體就是每一個(gè)分類的結(jié)構(gòu)
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;          //屬性列表
};
  • 1)夜焦、類的名字(name)
  • 2)壳澳、類(cls)
  • 3)、category中所有給類添加的實(shí)例方法的列表(instanceMethods)
  • 4)茫经、category中所有添加的類方法的列表(classMethods)
  • 5)巷波、category實(shí)現(xiàn)的所有協(xié)議的列表(protocols)
  • 6)、category中添加的所有屬性(instanceProperties)
  • _OBJC_$_CATEGORY_Person_$_DD :該結(jié)構(gòu)體是 Person+DD分類本身的初始化
static struct _category_t _OBJC_$_CATEGORY_Person_$_DD __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "Person",         //類名  
    0, // &OBJC_CLASS_$_Person,   // 類 cls
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_DD, //實(shí)例方法列表
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_DD, //類方法列表
    (const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Person_$_DD, //協(xié)議列表
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_DD,  //屬性列表
};

幾個(gè)結(jié)構(gòu)體的命名都遵循 公共前綴+類名+category名字 的命名方式卸伞,需要注意到的事實(shí)就是category的名字用來(lái)給各種列表以及后面的category結(jié)構(gòu)體本身命名抹镊,而且有static來(lái)修飾,所以在同一個(gè)編譯單元里我們的category名不能重復(fù)荤傲,否則會(huì)出現(xiàn)編譯錯(cuò)誤垮耳。(面試題:同一編譯單元Category能否重名?

  • _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_DD :實(shí)例方法列表 結(jié)構(gòu)體
static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[7];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_DD __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    7,
    {{(struct objc_selector *)"setHeight:", "v24@0:8@16", (void *)_I_Person_DD_setHeight_},
    {(struct objc_selector *)"height", "@16@0:8", (void *)_I_Person_DD_height},
    {(struct objc_selector *)"setWeight:", "v24@0:8@16", (void *)_I_Person_DD_setWeight_},
    {(struct objc_selector *)"weight", "@16@0:8", (void *)_I_Person_DD_weight},
    {(struct objc_selector *)"run_Category", "v16@0:8", (void *)_I_Person_DD_run_Category},
    {(struct objc_selector *)"requiredMethod", "v16@0:8", (void *)_I_Person_DD_requiredMethod},
    {(struct objc_selector *)"optionalMethod", "v16@0:8", (void *)_I_Person_DD_optionalMethod}}
};

此處遂黍,很容易發(fā)現(xiàn)幾個(gè)熟悉的方法名氨菇,都是分類中的 實(shí)例方法+setter/getter+protocol方法

  1. 實(shí)例方法:run_Category

  2. setter/getter:setHeight:儡炼、heightsetWeight:查蓉、weight

  3. protocol方法:requiredMethod乌询、optionalMethod

  • _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_DD : 類方法列表 結(jié)構(gòu)體
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_$_DD __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"eat_Category", "v16@0:8", (void *)_C_Person_DD_eat_Category}}
};

此處是分類中 類方法

  1. 類方法:eat_Category
  • _OBJC_CATEGORY_PROTOCOLS_$_Person_$_DD : 協(xié)議列表 結(jié)構(gòu)體
static struct /*_protocol_list_t*/ {
    long protocol_count;  // Note, this is 32/64 bit
    struct _protocol_t *super_protocols[1];
} _OBJC_CATEGORY_PROTOCOLS_$_Person_$_DD __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    1,
    &_OBJC_PROTOCOL_MyProtocol
};

此處為 協(xié)議列表結(jié)構(gòu)體,可以看出當(dāng)前Category只遵循了 _OBJC_PROTOCOL_MyProtocol 協(xié)議豌研。

對(duì)于協(xié)議列表結(jié)構(gòu)體 的深入了解妹田。可自行搜索鹃共。

  • _OBJC_$_PROP_LIST_Person_$_DD :屬性列表 結(jié)構(gòu)體
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_$_DD __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    2,
    {{"height","T@\"NSString\",C,N"},
    {"weight","T@\"NSNumber\",&,N"}}
};

此處是分類中新增的 屬性列表結(jié)構(gòu)體 鬼佣,可以看到分類為Person新增的兩個(gè)屬性 heightweight 霜浴。

三晶衷、Category如何加載(運(yùn)行時(shí)加載)

深入理解Objective-C:Category - 4、追本溯源-category如何加載

我們知道阴孟,Objective-C的運(yùn)行是依賴OC的runtime的晌纫,而OC的runtime和其他系統(tǒng)庫(kù)一樣,是OS X和iOS通過(guò)dyld動(dòng)態(tài)加載的永丝。

也就是如何將Category中新增的屬性與方法在運(yùn)行時(shí)加載到本類中锹漱。

想了解更多dyld地同學(xué)可以移步這里(3)。

四慕嚷、Category中+load方法哥牍、+initialize方法

我們先從幾個(gè)面試題開(kāi)始:

1)、在類的+load方法調(diào)用的時(shí)候喝检,我們可以調(diào)用category中聲明的方法么嗅辣?

? 答: 1)、可以調(diào)用挠说,因?yàn)楦郊觕ategory到類的工作會(huì)先于+load方法的執(zhí)行

2)辩诞、Category中有l(wèi)oad方法嗎?load方法是什么時(shí)候調(diào)用纺涤?

? 答:2)译暂、有,+load的執(zhí)行順序是先類撩炊,后category外永,而category的+load執(zhí)行順序是根據(jù)編譯順序決定的。

3)拧咳、load伯顶,initialize的區(qū)別是什么?它們?cè)贑ategory中的調(diào)用順序以及出現(xiàn)繼承時(shí)它們之間的調(diào)用過(guò)程是怎么樣的?

4.1祭衩、+load方法在Category中的調(diào)用順序

+load的執(zhí)行順序是先類灶体,后category,而category的+load執(zhí)行順序是根據(jù)編譯順序決定的掐暮。

image
2019-07-29 14:48:38.461856+0800 Category深入[4543:177912] MyClass load
2019-07-29 14:48:38.462248+0800 Category深入[4543:177912] MyClass+Category_1 load
2019-07-29 14:48:38.462303+0800 Category深入[4543:177912] MyClass+Category_2 load
image
2019-07-29 14:47:46.286189+0800 Category深入[4526:177145] MyClass load
2019-07-29 14:47:46.286580+0800 Category深入[4526:177145] MyClass+Category_2 load
2019-07-29 14:47:46.286647+0800 Category深入[4526:177145] MyClass+Category_1 load

4.2蝎抽、有繼承關(guān)系的類中,+load調(diào)用順序

image

父類:Father

父類分類:Father+Category_1 和 Father+Category_2

子類:Son

子類分類:Son+Category_1 和 Son+Category_2

// 輸出
2019-07-29 16:20:57.900182+0800 Category深入[5664:229891] Father load
2019-07-29 16:20:57.900585+0800 Category深入[5664:229891] Son load
2019-07-29 16:20:57.900681+0800 Category深入[5664:229891] Father Cagetory_2 load
2019-07-29 16:20:57.900734+0800 Category深入[5664:229891] Son Category_1 load
2019-07-29 16:20:57.900782+0800 Category深入[5664:229891] Father Category_1 load
2019-07-29 16:20:57.900845+0800 Category深入[5664:229891] Son Category_2 load

1路克、父類+load 在 子類之前調(diào)用樟结;子類+load在分類之前調(diào)用;(修改編譯順序也一樣)

2精算、當(dāng)有多個(gè)類別(Category)都實(shí)現(xiàn)了load方法瓢宦,這幾個(gè)load方法都會(huì)執(zhí)行,但執(zhí)行順序不確定灰羽,執(zhí)行順序與其在Compile Sources中出現(xiàn)的順序一致;

為什么會(huì)這樣呢驮履?

細(xì)節(jié)查看: Category的本質(zhì)<二>load,initialize方法 - 有繼承關(guān)系時(shí)load方法的調(diào)用順序

4.3廉嚼、+initialize方法調(diào)用順序

Category的本質(zhì)<二>load玫镐,initialize方法

情況一:只有父類和子類

// NewFather
+(void)initialize {
    NSLog(@"NewFather initialize");
}

// NewSon
+(void)initialize {
    NSLog(@"NewSon initialize");
}


// 測(cè)試一:調(diào)用父類
[NewFather alloc];
// 輸出
2019-08-01 09:03:11.547651+0800 Category深入[1088:16966] NewFather initialize


// 測(cè)試二:調(diào)用子類
[NewSon alloc];
// 輸出
2019-08-01 09:02:30.083591+0800 Category深入[1072:16383] NewFather initialize
2019-08-01 09:02:30.083668+0800 Category深入[1072:16383] NewSon initialize


// 測(cè)試三:若子類未實(shí)現(xiàn) +(void)initialize 方法,調(diào)用子類
[NewSon alloc];
// 輸出
2019-08-01 09:09:22.596737+0800 Category深入[1168:20045] NewFather initialize
2019-08-01 09:09:22.596811+0800 Category深入[1168:20045] NewFather initialize

  • initialize在類第一次接收到消息時(shí)調(diào)用前鹅,也就是objc_msgSend()。
  • 先調(diào)用父類的+initialize峭梳,再調(diào)用子類的initialize舰绘。
  • 如果子類沒(méi)有實(shí)現(xiàn)+initialize方法,會(huì)調(diào)用父類的+initialize(所以父類的+initialize方法可能會(huì)被調(diào)用多次)

情況二:父類葱椭、子類和分類

// NewFather
+(void)initialize {
    NSLog(@"NewFather initialize");
}
// NewFather+Category_1
+(void)initialize {
    NSLog(@"NewFather Category_1 initialize");
}
// NewFather+Category_2
+(void)initialize {
    NSLog(@"NewFather Category_2 initialize");
}

// NewSon
+(void)initialize {
    NSLog(@"NewSon initialize");
}
// NewSon+Category_1
+(void)initialize {
    NSLog(@"NewSon Category_1 initialize");
}
// NewSon+Category_2
+(void)initialize {
    NSLog(@"NewSon Category_2 initialize");
}


// 調(diào)用父類
[NewFather alloc];
// 輸出
2019-08-01 09:22:06.398530+0800 Category深入[1349:26686] NewFather Category_2 initialize

// 調(diào)用子類
[NewSon alloc];
// 輸出
2019-08-01 09:25:37.274370+0800 Category深入[1454:29643] NewFather Category_2 initialize
2019-08-01 09:25:37.274441+0800 Category深入[1454:29643] NewSon Category_2 initialize
  • 如果分類實(shí)現(xiàn)了+initialize捂寿,會(huì)覆蓋類本身的+initialize調(diào)用;

  • 多個(gè)分類時(shí)孵运,分類執(zhí)行順序可以通過(guò)targets -> Build Phases -> Complie Source進(jìn)行調(diào)節(jié)秦陋,注意執(zhí)行順序是從上到下的;

  • 如果子類和子類的分類都不實(shí)現(xiàn)+initialize治笨,則顯示父類的分類的+initialize方法驳概;

4.4、+load與+initialize比較

  • load

    Invoked whenever a class or category is added to the Objective-C runtime; implement this method to perform class-specific behavior upon loading.

  • initialize

    Initializes the class before it receives its first message.

iOS類方法load和initialize詳解

+(void)load +(void)initialize
調(diào)用方式 +load是通過(guò)函數(shù)指針指向函數(shù)旷赖,拿到函數(shù)地址顺又,直接通過(guò)內(nèi)存地址查找調(diào)用的。 +initialize是通過(guò)objc_msgSend()進(jìn)行調(diào)用等孵,isa和superclass來(lái)尋找的
調(diào)用時(shí)機(jī) 只要文件被引用就會(huì)被調(diào)用稚照,所以如果類沒(méi)有被引進(jìn)項(xiàng)目,就不會(huì)調(diào)用 +load 是在類或者它的子類收到第一條消息(實(shí)例方法、類方法)之前被調(diào)用的。
調(diào)用順序 1果录、+load 會(huì)在 main() 函數(shù)之前被調(diào)用上枕;
2、父類 > 子類 > 分類
父類 > 子類(或分類弱恒,分類覆蓋本類方法)
調(diào)用次數(shù) 1次 1辨萍、如果只有父類,則調(diào)用1次或0次斤彼;
2分瘦;有子類則調(diào)用多次;(子類也會(huì)調(diào)用父類的initialize方法)
子類琉苇、類別調(diào)用 子類:如果子類沒(méi)有實(shí)現(xiàn) load 方法, 該子類是不會(huì)調(diào)用該方法的, 就算父類實(shí)現(xiàn)了也不會(huì)調(diào)用父類的load方法嘲玫;

類別:當(dāng)有多個(gè)類別(Category)都實(shí)現(xiàn)了load方法,這幾個(gè)load方法都會(huì)執(zhí)行,但執(zhí)行順序不確定,執(zhí)行順序與其在Compile Sources中出現(xiàn)的順序一致;
image
如果子類實(shí)現(xiàn) initialize方法時(shí),會(huì)覆蓋父類initialize方法并扇;

如果子類不實(shí)現(xiàn) initialize 方法去团,會(huì)把父類的實(shí)現(xiàn)繼承過(guò)來(lái)調(diào)用一遍;

當(dāng)有多個(gè)Category都實(shí)現(xiàn)了initialize方法,會(huì)覆蓋類中的方法,只執(zhí)行一個(gè)(會(huì)執(zhí)行Compile Sources 列表中最后一個(gè)Category 的initialize方法)
線程安全 load 方法是線程安全的穷蛹,內(nèi)部使用了鎖土陪,應(yīng)避免線程阻塞在 load 中。 在initialize方法收到調(diào)用時(shí)肴熏,運(yùn)行環(huán)境基本健全鬼雀。initialize的運(yùn)行過(guò)程中是能保證線程安全的;
常見(jiàn)場(chǎng)景 1蛙吏、由于調(diào)用load方法時(shí)的環(huán)境很不安全源哩,我們應(yīng)該盡量減少load方法的邏輯;
2鸦做、load 中實(shí)現(xiàn) Method Swizzle
1励烦、常用于初始化全局變量和靜態(tài)變量;
2泼诱、者單例模式的實(shí)現(xiàn)方案坛掠;

五、Category和方法覆蓋

5.1治筒、覆蓋本類方法(類方法和實(shí)例方法)

// 本類 方法
+(void)getName;
-(void)getAge;

// Stu_Category_1 分類覆蓋實(shí)現(xiàn)
// Stu_Category_2 分類覆蓋實(shí)現(xiàn)

// 輸出
2019-07-29 15:29:09.597718+0800 Category深入[5074:203420] Stu_Category_2 getName
2019-07-29 15:29:09.597790+0800 Category深入[5074:203420] Stu_Category_2 getAge

交換下編譯順序

image
// 輸出
2019-07-29 15:36:48.679354+0800 Category深入[5140:208264] Stu_Category_1 getName
2019-07-29 15:36:48.679421+0800 Category深入[5140:208264] Stu_Category_1 getAge

結(jié)論:

1)屉栓、category的方法沒(méi)有“完全替換掉”原來(lái)類已經(jīng)有的方法,也就是說(shuō)如果category和原來(lái)類都有methodA耸袜,那么category附加完成之后系瓢,類的方法列表里會(huì)有兩個(gè)methodA;

2)句灌、category的方法被放到了新方法列表的前面夷陋,而原來(lái)類的方法被放到了新方法列表的后面欠拾,這也就是我們平常所說(shuō)的category的方法會(huì)“覆蓋”掉原來(lái)類的同名方法,這是因?yàn)檫\(yùn)行時(shí)在查找方法的時(shí)候是順著方法列表的順序查找的骗绕,它只要一找到對(duì)應(yīng)名字的方法藐窄,就會(huì)罷休_,殊不知后面可能還有一樣名字的方法酬土。

5.2荆忍、怎么調(diào)用到原來(lái)類中被category覆蓋掉的方法?

? 對(duì)于這個(gè)問(wèn)題撤缴,我們已經(jīng)知道category其實(shí)并不是完全替換掉原來(lái)類的同名方法刹枉,只是category在方法列表的前面而已,所以我們只要順著方法列表找到最后一個(gè)對(duì)應(yīng)名字的方法屈呕,就可以調(diào)用原來(lái)類的方法:

美團(tuán)技術(shù) - 深入理解Objective-C:Category 第6節(jié):觸類旁通-category和方法覆蓋

六微宝、Category和關(guān)聯(lián)對(duì)象

6.1、為什么category不能添加成員變量虎眨?

解釋一:從Objective-C類是由Class類型來(lái)分析

Objective-C類是由Class類型來(lái)表示的蟋软,它實(shí)際上是一個(gè)指向objc_class結(jié)構(gòu)體的指針。它的定義如下:

typedef struct objc_class *Class;

objc_class結(jié)構(gòu)體的定義如下:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    Class super_class                       OBJC2_UNAVAILABLE;  // 父類
    const char *name                        OBJC2_UNAVAILABLE;  // 類名
    long version                            OBJC2_UNAVAILABLE;  // 類的版本信息嗽桩,默認(rèn)為0
    long info                               OBJC2_UNAVAILABLE;  // 類信息岳守,供運(yùn)行期使用的一些位標(biāo)識(shí)
    long instance_size                      OBJC2_UNAVAILABLE;  // 該類的實(shí)例變量大小
    struct objc_ivar_list *ivars            OBJC2_UNAVAILABLE;  // 該類的成員變量鏈表
    struct objc_method_list **methodLists   OBJC2_UNAVAILABLE;  // 方法定義的鏈表
    struct objc_cache *cache                OBJC2_UNAVAILABLE;  // 方法緩存
    struct objc_protocol_list *protocols    OBJC2_UNAVAILABLE;  // 協(xié)議鏈表
#endif
} OBJC2_UNAVAILABLE;

在上面的objc_class結(jié)構(gòu)體中,ivars是objc_ivar_list(成員變量列表)指針碌冶;methodLists是指向objc_method_list指針的指針湿痢。在Runtime中,objc_class結(jié)構(gòu)體大小是固定的扑庞,不可能往這個(gè)結(jié)構(gòu)體中添加數(shù)據(jù)譬重,只能修改。所以ivars指向的是一個(gè)固定區(qū)域嫩挤,只能修改成員變量值害幅,不能增加成員變量個(gè)數(shù)消恍。methodList是一個(gè)二維數(shù)組岂昭,所以可以修改methodLists的值來(lái)增加成員方法,雖沒(méi)辦法擴(kuò)展methodLists指向的內(nèi)存區(qū)域狠怨,卻可以改變這個(gè)內(nèi)存區(qū)域的值(存儲(chǔ)的是指針)约啊。因此,可以動(dòng)態(tài)添加方法佣赖,不能添加成員變量恰矩。

解釋二:從Category底層結(jié)構(gòu)體category_t看

首先我們從Category底層結(jié)構(gòu)體category_t(在objc-runtime-new.h中可以找到此定義)來(lái)看下:

struct category_t {
    const char *name;
    classref_t cls;
    struct method_list_t *instanceMethods;
    struct method_list_t *classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;
    // Fields below this point are not always present on disk. 
    // 譯:此點(diǎn)下方的字段并不總是出現(xiàn)在磁盤(pán)上
    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);
};

通過(guò)分類的底層結(jié)構(gòu)我們可以看到,分類中可以存放實(shí)例方法憎蛤,類方法外傅,協(xié)議纪吮,屬性,但是沒(méi)有存放成員變量的地方萎胰。所以不能添加成員變量碾盟。

6.2、如何給Category添加成員變量技竟?

如何給NSArray添加一個(gè)屬性(不能使用繼承)冰肴?不能用繼承,難道用分類榔组?但是分類只能添加方法不能添加屬性拔跷尽(Category不允許為已有的類添加新的成員變量,實(shí)際上允許添加屬性的搓扯,同樣可以使用@property检痰,但是不會(huì)生成_變量(帶下劃線的成員變量),也不會(huì)生成添加屬性的getter和setter方法擅编,所以攀细,盡管添加了屬性,也無(wú)法使用點(diǎn)語(yǔ)法調(diào)用getter和setter方法爱态。但實(shí)際上可以使用runtime去實(shí)現(xiàn)Category為已有的類添加新的屬性并生成getter和setter方法):

// .h 文件
@interface Person (DD) 
@property (nonatomic, copy) NSString *height;
@end

// .m 文件
@implementation Person (DD)
#pragma mark - setter/getter
-(void)setHeight:(NSString *)height {
    objc_setAssociatedObject(self, @selector(height), height, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)height {
    return objc_getAssociatedObject(self, _cmd);
}
@end
關(guān)聯(lián)策略 對(duì)應(yīng)修飾符
OBJC_ASSOCIATION_ASSIGN @property(assign)
OBJC_ASSOCIATION_RETAIN_NONATOMIC @property(strong, nonatomic)
OBJC_ASSOCIATION_COPY_NONATOMIC @property(copy, nonatomic)
OBJC_ASSOCIATION_RETAIN @property(strong, atomic)
OBJC_ASSOCIATION_COPY @property(copy, atomic)

6.3谭贪、關(guān)聯(lián)對(duì)象又是存在什么地方呢? 如何存儲(chǔ)锦担? 對(duì)象銷毀時(shí)候如何處理關(guān)聯(lián)對(duì)象呢俭识?

美團(tuán)技術(shù) - 深入理解Objective-C:Category - 第七節(jié):更上一層-category和關(guān)聯(lián)對(duì)象

七、Category與Extension

extension看起來(lái)很像一個(gè)匿名的category洞渔,但是extension和有名字的category幾乎完全是兩個(gè)東西套媚。 extension在編譯期決議,它就是類的一部分磁椒,在編譯期和頭文件里的@interface以及實(shí)現(xiàn)文件里的@implement一起形成一個(gè)完整的類堤瘤,它伴隨類的產(chǎn)生而產(chǎn)生,亦隨之一起消亡浆熔。extension一般用來(lái)隱藏類的私有信息本辐,你必須有一個(gè)類的源碼才能為一個(gè)類添加extension,所以你無(wú)法為系統(tǒng)的類比如NSString添加extension医增。

但是category則完全不一樣慎皱,它是在運(yùn)行期決議的。 就category和extension的區(qū)別來(lái)看叶骨,我們可以推導(dǎo)出一個(gè)明顯的事實(shí)茫多,extension可以添加實(shí)例變量,而category是無(wú)法添加實(shí)例變量的(因?yàn)樵谶\(yùn)行期忽刽,對(duì)象的內(nèi)存布局已經(jīng)確定天揖,如果添加實(shí)例變量就會(huì)破壞類的內(nèi)部布局夺欲,這對(duì)編譯型語(yǔ)言來(lái)說(shuō)是災(zāi)難性的)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末今膊,一起剝皮案震驚了整個(gè)濱河市洁闰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌万细,老刑警劉巖扑眉,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異赖钞,居然都是意外死亡腰素,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)雪营,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)弓千,“玉大人,你說(shuō)我怎么就攤上這事献起⊙蠓茫” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵谴餐,是天一觀的道長(zhǎng)姻政。 經(jīng)常有香客問(wèn)我,道長(zhǎng)岂嗓,這世上最難降的妖魔是什么汁展? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮厌殉,結(jié)果婚禮上食绿,老公的妹妹穿的比我還像新娘。我一直安慰自己公罕,他們只是感情好器紧,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著楼眷,像睡著了一般铲汪。 火紅的嫁衣襯著肌膚如雪锰提。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天谒所,我揣著相機(jī)與錄音蝶俱,去河邊找鬼。 笑死蕾哟,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播芦拿,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼士飒,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了蔗崎?” 一聲冷哼從身側(cè)響起酵幕,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎缓苛,沒(méi)想到半個(gè)月后芳撒,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡未桥,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年笔刹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冬耿。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡舌菜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出亦镶,到底是詐尸還是另有隱情日月,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布缤骨,位于F島的核電站爱咬,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏绊起。R本人自食惡果不足惜台颠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望勒庄。 院中可真熱鬧串前,春花似錦、人聲如沸实蔽。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)局装。三九已至坛吁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間铐尚,已是汗流浹背拨脉。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留宣增,地道東北人玫膀。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像爹脾,于是被迫代替她去往敵國(guó)和親帖旨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子箕昭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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