Class 類型對(duì)象
OC本身是一種強(qiáng)類型語(yǔ)言钝的,但其運(yùn)行時(shí)功能讓它又有了動(dòng)態(tài)語(yǔ)言的特點(diǎn)猴誊。OC中對(duì)象的類型和對(duì)象所執(zhí)行的方法都是在運(yùn)行時(shí)階段進(jìn)行查找并確認(rèn)的艰赞,這種機(jī)制被稱為動(dòng)態(tài)綁定莱褒。想要弄清楚運(yùn)行時(shí)如何能夠?qū)崿F(xiàn)動(dòng)態(tài)綁定機(jī)制浴栽,首先要了解OC中對(duì)象的本質(zhì)荒叼。
OC是C語(yǔ)言的超集,所以O(shè)C中面向?qū)ο蟮墓δ茉诘讓右彩鞘褂肅語(yǔ)言來(lái)實(shí)現(xiàn)典鸡。我們?cè)贠C中使用的對(duì)象被廓,通常指的是儲(chǔ)存該對(duì)象內(nèi)存地址的一個(gè)指針變量(Java中稱為引用),因此我們?cè)贠C中聲明對(duì)象時(shí)通常使用類型名稱加一個(gè)*
號(hào)萝玷,稍微了解C語(yǔ)言的人都知道*
號(hào)代表該變量是一個(gè)指針變量嫁乘。OC中還有一個(gè)特殊的類型id
,它可以表示通用類型的OC對(duì)象球碉,因?yàn)樗旧砭捅欢x為一種特殊的指針變量蜓斧,所以不需要在id后面再加一個(gè)*
號(hào)。
NSString *someString = @"Some String";
id otherString = @"Other String";
[someString count]; // 編譯期報(bào)錯(cuò)
[otherString count]; // 運(yùn)行時(shí)報(bào)錯(cuò)
使用上述兩種方式聲明對(duì)象睁冬,在語(yǔ)法意義上其實(shí)完全相同法精,因?yàn)閷?duì)象的具體類型只在運(yùn)行時(shí)才會(huì)被確認(rèn)。唯一的區(qū)別在于痴突,如果聲明時(shí)使用了具體類型信息,編譯器會(huì)在編譯期間查找對(duì)象所能執(zhí)行的方法狼荞,找不到就會(huì)報(bào)錯(cuò)辽装;而id代表通用類型的對(duì)象,編譯器默認(rèn)它能夠執(zhí)行任何已存在的方法相味。
我們可以在蘋(píng)果官方的運(yùn)行時(shí)庫(kù)的頭文件中查看id類型的定義:
struct objc_object {
Class isa;
};
typedef struct objc_object *id;
可以看出id本質(zhì)是一個(gè)C語(yǔ)言結(jié)構(gòu)體拾积,該結(jié)構(gòu)體只有一個(gè)Class類型的成員isa
(取意is a,是一個(gè))丰涉,代表著對(duì)象所屬的具體類型拓巧。其實(shí)在NSObject類的頭文件中,同樣聲明有一個(gè)這樣的實(shí)例變量isa一死。因此肛度,可以說(shuō)OC中任何對(duì)象,都會(huì)默認(rèn)帶有一個(gè)實(shí)例變量isa用來(lái)儲(chǔ)存對(duì)象的具體類型信息投慈。
Class的定義也可以在運(yùn)行時(shí)庫(kù)的頭文件中查看:
struct objc_class {
Class isa;
Class super_class;
const char *name;
long version;
long info;
long instance_size;
struct objc_ivar_list *ivars;
struct objc_method_list **methodLists;
struct objc_cache *cache;
struct objc_protocol_list *protocols;
};
typedef struct objc_class *Class;
此結(jié)構(gòu)體可以儲(chǔ)存類的諸多信息承耿,例如類型名冠骄、父類類型、實(shí)例變量列表加袋、方法列表等凛辣,這些信息被稱作類的元數(shù)據(jù)(metadata)
。該結(jié)構(gòu)體也有一個(gè)Class類型的成員isa职烧,說(shuō)明Class本身也是一個(gè)OC對(duì)象(被稱為類對(duì)象或類型對(duì)象)扁誓,而它的對(duì)象類型(isa所指向的類型)被稱為元類(metaclass)
,元類中儲(chǔ)存的是類對(duì)象的元數(shù)據(jù)蚀之,比如類方法就儲(chǔ)存在這里蝗敢。每個(gè)類可以有無(wú)數(shù)個(gè)對(duì)象,但僅有一個(gè)類對(duì)象恬总,也僅有一個(gè)與之對(duì)應(yīng)的元類前普。
對(duì)象、類對(duì)象和元類的關(guān)系如下圖所示:
由于類對(duì)象和isa指針的存在壹堰,OC中的所有對(duì)象都可以在運(yùn)行時(shí)查找自己的真實(shí)類型拭卿,并確定自己所能執(zhí)行的方法。當(dāng)真正給對(duì)象發(fā)送一條消息(或稱為調(diào)用方法)時(shí)贱纠,運(yùn)行時(shí)機(jī)制會(huì)對(duì)該消息進(jìn)行一系列復(fù)雜的處理峻厚,接下來(lái)我們就繼續(xù)討論運(yùn)行時(shí)的消息處理。
Message Dispatch 消息派發(fā)
調(diào)用對(duì)象的某個(gè)方法(或稱為給對(duì)象發(fā)送某個(gè)消息)是面向?qū)ο缶幊讨凶畛J褂玫墓δ茏缓浮T贠C中惠桃,由于動(dòng)態(tài)綁定機(jī)制使得程序直到運(yùn)行時(shí)才能清楚那個(gè)方法需要被執(zhí)行,甚至通過(guò)使用底層的運(yùn)行時(shí)函數(shù)辖试,就可以更改調(diào)用的方法或改變方法內(nèi)部的功能實(shí)現(xiàn)辜王,這些特性使得OC成為一門真正的動(dòng)態(tài)語(yǔ)言。
id returnValue = [someObject messageName:param];
OC的消息處理罐孝,在底層也是使用C語(yǔ)言函數(shù)來(lái)實(shí)現(xiàn)呐馆,與消息處理功能相對(duì)應(yīng)的函數(shù)叫做objc_msgSend
,該函數(shù)在頭文件中的聲明如下:
id objc_msgSend(id self, SEL op, ...)
可以看出objc_msgSend是一個(gè)可變參數(shù)函數(shù)莲兢,其中第一個(gè)參數(shù)代表消息的接收者汹来,第二個(gè)參數(shù)代表消息的選擇器,后續(xù)參數(shù)表示消息發(fā)送時(shí)附帶的參數(shù)改艇。編譯器在編譯期間就會(huì)將發(fā)送消息的代碼轉(zhuǎn)換為objc_msgSend函數(shù)收班。
id returnValue = objc_msgSend(someObject, @selector(messageName:), param);
在運(yùn)行時(shí)階段,objc_msgSend函數(shù)內(nèi)部會(huì)根據(jù)消息的接收者和選擇器來(lái)選擇調(diào)用適當(dāng)?shù)姆椒ㄚ诵帧橥瓿纱瞬僮鳎?em>objc_msgSend函數(shù)首先會(huì)根據(jù)消息接收者對(duì)象的isa指針找到它的真實(shí)類型摔桦,然后在該類對(duì)象的方法列表中查找是否有與當(dāng)前選擇器相對(duì)應(yīng)的方法,如果有則跳轉(zhuǎn)到該方法執(zhí)行承疲;如果沒(méi)有找到酣溃,則會(huì)按照類的繼承體系向上繼續(xù)查找瘦穆,一旦找到就跳轉(zhuǎn)過(guò)去執(zhí)行目標(biāo)方法。當(dāng)最終都沒(méi)有找到與當(dāng)前選擇器相對(duì)應(yīng)的方法時(shí)赊豌,運(yùn)行時(shí)機(jī)制則會(huì)開(kāi)啟消息轉(zhuǎn)發(fā)流程扛或,我們接下來(lái)就繼續(xù)討論運(yùn)行時(shí)的消息轉(zhuǎn)發(fā)。
Message Forward 消息轉(zhuǎn)發(fā)
消息轉(zhuǎn)發(fā)流程比較復(fù)雜碘饼,主要分三個(gè)步驟熙兔,首先我們來(lái)看一張消息轉(zhuǎn)發(fā)完整的流程圖。
第一步
當(dāng)消息派發(fā)流程最終在對(duì)象的類和父類中都沒(méi)有找到對(duì)應(yīng)選擇器的方法時(shí)艾恼,就會(huì)開(kāi)啟消息轉(zhuǎn)發(fā)流程住涉。首先,第一步會(huì)先調(diào)用消息接收者所在類的resolveInstanceMethod:
方法钠绍,該方法返回一個(gè)BOOL值舆声,表示是否動(dòng)態(tài)添加一個(gè)方法來(lái)響應(yīng)當(dāng)前消息選擇器。如果發(fā)送的消息是一個(gè)類方法柳爽,則會(huì)調(diào)用另一個(gè)類似的方法resolveClassMethod:
媳握。
+ (BOOL)resolveInstanceMethod:(SEL)sel;
+ (BOOL)resolveClassMethod:(SEL)sel;
以上兩個(gè)方法均聲明在NSObject類中,如果消息接收者所在類重寫(xiě)了resolveInstanceMethod:
方法并返回YES磷脯,也就意味著想要?jiǎng)討B(tài)添加一個(gè)方法來(lái)響應(yīng)當(dāng)前的消息選擇器蛾找,可以在重寫(xiě)的方法內(nèi)使用class_addMethod
函數(shù)來(lái)為當(dāng)前類添加方法。
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
該函數(shù)第一個(gè)參數(shù)代表為哪個(gè)類添加方法赵誓,第二個(gè)參數(shù)是方法所對(duì)應(yīng)的選擇器打毛,第三個(gè)參數(shù)是C語(yǔ)言的函數(shù)指針,用來(lái)指向待添加的方法俩功,最后一個(gè)參數(shù)表示待添加方法的類型編碼(詳情可查看蘋(píng)果官方文檔:Objective-C Runtime Programming Guide)幻枉。
第二步
如果上一步過(guò)程中,并沒(méi)有新方法能響應(yīng)消息選擇器诡蜓,則會(huì)進(jìn)入消息轉(zhuǎn)發(fā)流程的第二步熬甫。在第二步中系統(tǒng)會(huì)調(diào)用當(dāng)前消息接收者所在類的forwardingTargetForSelector:
方法,用以詢問(wèn)能否將該條消息發(fā)送給其他接收者來(lái)處理万牺,方法的返回值就代表這個(gè)新的接收者,如果不允許將消息轉(zhuǎn)發(fā)給其他接收者則返回nil洽腺。
- (id)forwardingTargetForSelector:(SEL)aSelector;
利用這個(gè)方法脚粟,我們可以使用組合的方式模擬出多重繼承的特性。比如可以在一個(gè)類中擁有一系列其他類型的屬性蘸朋,然后重寫(xiě)forwardingTargetForSelector:
方法核无,根據(jù)這些屬性所能響應(yīng)的消息選擇器返回對(duì)應(yīng)的屬性對(duì)象,這樣在外界看起來(lái)藕坯,該類的對(duì)象就好像是能夠處理多種不同類型的方法了。
第三步
如果forwardingTargetForSelector:
方法的返回值為nil,那么消息轉(zhuǎn)發(fā)機(jī)制還要繼續(xù)進(jìn)行最后一步碍舍。在這一步中酥宴,系統(tǒng)會(huì)將尚未處理的消息包裝成一個(gè)NSInvocation
對(duì)象,其內(nèi)部包含與該消息相關(guān)的所有信息窑滞,比如消息的選擇器、目標(biāo)接收者、參數(shù)等局义。之后系統(tǒng)會(huì)調(diào)用消息接收者所在類的forwardInvocation:
方法,并將生成的NSInvocation
對(duì)象作為參數(shù)傳入冗疮。
- (void)forwardInvocation:(NSInvocation *)anInvocation;
forwardInvocation:
方法同樣聲明在NSObject類中萄唇,我們可以重寫(xiě)該方法的實(shí)現(xiàn)。比如將NSInvocation
對(duì)象的target
屬性設(shè)置為其他接收者术幔,此操作可以實(shí)現(xiàn)與上一步操作同樣的效果另萤,但明顯在效率上沒(méi)有第二步的操作高,所以很少有人在這一步中僅僅只是改變消息的接收者诅挑。NSInvocation
類中還提供了許多屬性和方法用于修改其對(duì)應(yīng)方法的信息四敞,比如可以修改方法的參數(shù)和返回值,或者直接更改消息選擇器轉(zhuǎn)而調(diào)用其他方法揍障。
如果消息接收者在這一步中仍然無(wú)法響應(yīng)消息選擇器目养,那么系統(tǒng)會(huì)自動(dòng)調(diào)用doesNotRecognizeSelector:
方法,該方法默認(rèn)實(shí)現(xiàn)為拋出異常毒嫡,也就是我們?cè)陂_(kāi)發(fā)中經(jīng)常見(jiàn)到的unrecognized selector sent to instance癌蚁。
-[ViewController count]: unrecognized selector sent to instance
消息轉(zhuǎn)發(fā)示例
現(xiàn)在再回頭看我們之前消息轉(zhuǎn)發(fā)完整的流程圖,應(yīng)該能夠更清晰地了解系統(tǒng)執(zhí)行每一步操作的目的和作用了兜畸。接下來(lái)我們用一個(gè)示例來(lái)演示如何利用消息轉(zhuǎn)發(fā)機(jī)制來(lái)自定義一個(gè)字典類努释,該字典類的對(duì)象可以直接使用屬性方式來(lái)存取內(nèi)容。完整的示例代碼如下咬摇。
// WXGAutoDictionary.h
#import <Foundation/Foundation.h>
@interface WXGAutoDictionary : NSObject
// 可供存儲(chǔ)的屬性伐蒂,可以為任意OC對(duì)象
@property (nonatomic, strong) id obj;
@end
// WXGAutoDictionary.m
#import "WXGAutoDictionary.h"
#import <objc/runtime.h>
@interface WXGAutoDictionary ()
@property (nonatomic, strong) NSMutableDictionary *backStore; // 后臺(tái)存儲(chǔ)用字典
@end
@implementation WXGAutoDictionary
@dynamic obj; // 禁止編譯器自動(dòng)生成getter和setter方法
- (instancetype)init {
if (self = [super init]) {
_backStore = @{}.mutableCopy; // 初始化字典
}
return self;
}
// 重寫(xiě)此方法,允許動(dòng)態(tài)添加方法來(lái)響應(yīng)指定的消息選擇器
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *selString = NSStringFromSelector(sel);
// 類型編碼:v->void @->OC對(duì)象 :->SEL選擇器
// 響應(yīng)setter方法的選擇器
if ([selString hasPrefix:@"set"]) {
class_addMethod(self, sel, (IMP)autoDictionarySetter, "v@:@");
} else { // 響應(yīng)getter方法的選擇器
class_addMethod(self, sel, (IMP)autoDictionaryGetter, "@@:");
}
return YES;
}
// 處理setter方法的函數(shù)
void autoDictionarySetter(id self, SEL sel, id value) {
WXGAutoDictionary *autoDict = (WXGAutoDictionary *)self;
NSMutableDictionary *backStore = autoDict.backStore;
NSString *selString = NSStringFromSelector(sel);
NSMutableString *key = selString.mutableCopy;
[key deleteCharactersInRange:NSMakeRange(key.length - 1, 1)];
[key deleteCharactersInRange:NSMakeRange(0, 3)];
[key replaceCharactersInRange:NSMakeRange(0, 1) withString:[[key substringToIndex:1] lowercaseString]];
if (value) {
[backStore setObject:value forKey:key];
} else {
[backStore removeObjectForKey:key];
}
}
// 處理getter方法的函數(shù)
id autoDictionaryGetter(id self, SEL sel) {
WXGAutoDictionary *autoDict = (WXGAutoDictionary *)self;
NSMutableDictionary *backStore = autoDict.backStore;
NSString *key = NSStringFromSelector(sel);
return [backStore objectForKey:key];
}
@end
在外部使用該類非常簡(jiǎn)單肛鹏,示例代碼如下逸邦。
// main.m
#import <Foundation/Foundation.h>
#import "WXGAutoDictionary.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
WXGAutoDictionary *dict = [[WXGAutoDictionary alloc] init];
dict.obj = [NSDate date];
NSLog(@"%@", dict.obj); // 控制臺(tái)輸出當(dāng)前日期
}
return 0;
}
在程序開(kāi)始運(yùn)行后,dict對(duì)象所在的類中并沒(méi)有響應(yīng)setter和getter選擇器的方法在扰,消息派發(fā)階段無(wú)法在類對(duì)象的方法列表中找到合適的方法缕减,所以會(huì)進(jìn)入消息轉(zhuǎn)發(fā)流程。我們?cè)?code>resolveInstanceMethod:方法中返回YES芒珠,并為不同選擇器指定了不同的方法去處理桥狡,從而實(shí)現(xiàn)通過(guò)屬性的setter和getter方法對(duì)字典進(jìn)行存取操作。當(dāng)有另一個(gè)類型的屬性需要使用同樣的功能時(shí),只需在WXGAutoDictionary
類中添加屬性裹芝,并將屬性聲明為@dynamic
即可部逮,屬性的存取操作會(huì)由運(yùn)行時(shí)系統(tǒng)動(dòng)態(tài)指定方法來(lái)完成。
Method Swizzing 方法調(diào)配
我們已經(jīng)了解了OC中對(duì)象的類型和消息處理機(jī)制嫂易,這些有助于我們進(jìn)一步了解OC運(yùn)行時(shí)的其他功能和特性兄朋。接下來(lái)就介紹其中一種叫做Method Swizzing(方法調(diào)配)
的技術(shù),該技術(shù)經(jīng)常被稱為iOS開(kāi)發(fā)中的黑魔法炬搭。
在介紹方法調(diào)配技術(shù)之前蜈漓,我們首先來(lái)了解一下OC中方法和消息選擇器之間的關(guān)系,因?yàn)槲覀兘?jīng)常會(huì)將他們混為一談宫盔。在運(yùn)行時(shí)頭文件中融虽,我們可以找到方法的底層結(jié)構(gòu)定義。
struct objc_method {
SEL method_name;
char *method_types;
IMP method_imp;
}
可以看出灼芭,每一個(gè)方法內(nèi)部都包含三個(gè)成員有额,第一個(gè)是選擇器代表方法的名字,第二個(gè)是方法的類型彼绷,其值是一個(gè)C語(yǔ)言字符串巍佑,可以參考前文講過(guò)類型編碼,最后一個(gè)是C語(yǔ)言中的函數(shù)指針寄悯,用以指向方法具體執(zhí)行的函數(shù)萤衰。我們可以把方法的內(nèi)部結(jié)構(gòu)理解為每一個(gè)SEL選擇器(可以當(dāng)做是方法名)對(duì)應(yīng)一個(gè)具體的IMP函數(shù)(可以當(dāng)做是方法的實(shí)現(xiàn)),這也是SEL被稱為選擇器的原因猜旬。這樣我們就可以更加清楚地理解消息派發(fā)時(shí)脆栋,系統(tǒng)是如何根據(jù)消息選擇器來(lái)查找對(duì)應(yīng)的方法并跳轉(zhuǎn)到方法的具體實(shí)現(xiàn)的了。
首先洒擦,當(dāng)對(duì)象接收到某個(gè)消息時(shí)椿争,編譯器首先將代碼轉(zhuǎn)換為objc_msgSend函數(shù),并將消息的接收者和選擇器當(dāng)做函數(shù)的參數(shù)傳入熟嫩,接下來(lái)系統(tǒng)會(huì)根據(jù)接收者的isa指針找到它所對(duì)應(yīng)的類秦踪,在類的元數(shù)據(jù)信息中找到該類所擁有的方法列表,然后遍歷方法列表掸茅,將每一個(gè)方法內(nèi)部的SEL選擇器同傳入的消息選擇器進(jìn)行匹配椅邓,當(dāng)找到相同的選擇器后,就根據(jù)方法內(nèi)部的IMP函數(shù)指針跳轉(zhuǎn)到方法的具體實(shí)現(xiàn)昧狮。當(dāng)然景馁,為了提高方法多次執(zhí)行的效率,系統(tǒng)會(huì)將遍歷查詢的結(jié)果緩存起來(lái)陵且,儲(chǔ)存在類的元數(shù)據(jù)信息中裁僧,此處就不再繼續(xù)深入討論。
了解清楚選擇器和方法實(shí)現(xiàn)之間的一對(duì)一關(guān)系后慕购,我們接下來(lái)開(kāi)始介紹方法調(diào)配技術(shù)聊疲,它其實(shí)就是利用運(yùn)行時(shí)提供的函數(shù)來(lái)動(dòng)態(tài)修改選擇器和方法實(shí)現(xiàn)之間的對(duì)應(yīng)關(guān)系的一種技術(shù)。利用這種技術(shù)沪悲,我們可以在運(yùn)行時(shí)為某個(gè)類添加選擇器或更改選擇器所對(duì)應(yīng)的方法實(shí)現(xiàn)获洲,甚至可以更換兩個(gè)已有選擇器所對(duì)應(yīng)的方法實(shí)現(xiàn),從而實(shí)現(xiàn)一種極其詭異的效果殿如。下面就寫(xiě)一段示例程序贡珊,通過(guò)方法調(diào)配技術(shù)來(lái)更換NSString類的大小寫(xiě)轉(zhuǎn)換方法的實(shí)現(xiàn)(僅供娛樂(lè)使用)。
// main.m
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
Method lowercase = class_getInstanceMethod([NSString class], @selector(lowercaseString));
Method uppercase = class_getInstanceMethod([NSString class], @selector(uppercaseString));
method_exchangeImplementations(lowercase, uppercase);
NSLog(@"%@ -- %@", [@"AbCd" lowercaseString], [@"AbCd" uppercaseString]);
// 輸出結(jié)果:ABCD -- abcd
}
return 0;
}
可以看到lowercaseString
方法返回的是大寫(xiě)字母涉馁,而uppercaseString
方法返回了小寫(xiě)字母门岔。
方法調(diào)配技術(shù)的作用肯定不在于此,那么開(kāi)發(fā)者通常如何使用這種技術(shù)呢烤送?在總結(jié)方法調(diào)配技術(shù)的用處之前寒随,我們先再來(lái)看一個(gè)示例程序。同樣以NSString類為例帮坚,我們?yōu)槠?code>lowercaseString方法增加一些日志輸出功能(不改變方法名妻往,只是更改方法的實(shí)現(xiàn))。你可能第一時(shí)間想到用繼承來(lái)實(shí)現(xiàn)該需求试和,然而當(dāng)項(xiàng)目中有多個(gè)類需要同樣需求時(shí)讯泣,你需要每個(gè)類都去繼承一下,然后還要保證別人都是去用你的子類而不是原本的父類阅悍,這樣顯然并不是一種很好的解決辦法好渠。此時(shí)我們就可以嘗試使用方法調(diào)配技術(shù),完整的示例代碼如下溉箕。
// NSString+Logging.h
#import <Foundation/Foundation.h>
@interface NSString (Logging)
- (NSString *)lowercaseStringWithLogging;
@end
// NSString+Logging.m
#import "NSString+Logging.h"
@implementation NSString (Logging)
- (NSString *)lowercaseStringWithLogging {
NSString *lowercaseString = [self lowercaseStringWithLogging];
NSLog(@"%@ -> %@", self, lowercaseString);
return lowercaseString;
}
// main.m
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "NSString+Logging.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Method lowercase = class_getInstanceMethod([NSString class], @selector(lowercaseString));
Method lowercaselogging = class_getInstanceMethod([NSString class], @selector(lowercaseStringWithLogging));
method_exchangeImplementations(lowercase, lowercaselogging);
[@"AbCd" lowercaseString];
// 輸出結(jié)果:AbCd -> abcd
}
return 0;
}
@end
我們?yōu)镹SString類添加一個(gè)分類晦墙,在分類中添加一個(gè)帶日志輸出功能的方法肴茄,注意在該方法的實(shí)現(xiàn)中,我們調(diào)用了這句代碼[self lowercaseStringWithLogging]
寡痰,這看上去應(yīng)該會(huì)使程序陷入死循環(huán),但不要忘了拦坠,我們?cè)?em>main方法中利用方法調(diào)配技術(shù)來(lái)交換原有類的方法和分類方法的實(shí)現(xiàn),所以這句代碼實(shí)際上執(zhí)行的是原本的類中的實(shí)現(xiàn)贞滨,并不會(huì)造成死循環(huán)拍棕。
通過(guò)上文的示例程序勺良,我們可以為那些完全不知道具體實(shí)現(xiàn)的方法(也稱為黑盒方法)增加日志輸出功能,這常用于程序的調(diào)試尚困。實(shí)際上,還有很多與此類似的需求事甜,既要增加功能谬泌,又需要與原有方法聯(lián)系很緊密,例如增加權(quán)限驗(yàn)證和緩存功能逻谦,這類需求常被人們稱為Aspect(切面)
掌实,與之對(duì)應(yīng)的編程概念叫做Aspect Oriented Programming(面向切面編程)
。面向切面編程的概念有許多優(yōu)點(diǎn)邦马,它將那些瑣碎的事物從主邏輯中分離出來(lái)潮峦,并將它們附加在與主邏輯相對(duì)應(yīng)的橫向切面中連帶執(zhí)行,是對(duì)面向?qū)ο缶幊痰囊环N補(bǔ)充勇婴。在OC中忱嘹,我們可以利用運(yùn)行時(shí)特性和方法調(diào)配技術(shù)來(lái)實(shí)現(xiàn)這類面向切面編程的需求。
寫(xiě)在最后
本文主要是自己整理的讀書(shū)筆記耕渴,并重新整理歸納拘悦,內(nèi)容只是OC運(yùn)行時(shí)的一部分,只為理順OC運(yùn)行時(shí)的基本概念橱脸,從而為理解其他運(yùn)行時(shí)特性打下基礎(chǔ)础米。比如OC中經(jīng)常使用的KVC和KVO,在理解本文這些運(yùn)行時(shí)基本概念后添诉,應(yīng)該更有助于理解它們的實(shí)現(xiàn)原理屁桑,感興趣的可以參考以下文章:
還有OC運(yùn)行時(shí)中的Associated Object(關(guān)聯(lián)對(duì)象)
概念,可以參考以下文章:
其他參考閱讀: