前面我們討論了Runtime中對類和對象的處理蜂大,及對成員變量與屬性的處理婴程。這一章鲤氢,我們就要開始討論Runtime中最有意思的一部分:消息處理機制飘痛。我們將詳細(xì)討論消息的發(fā)送及消息的轉(zhuǎn)發(fā)晚胡。不過在討論消息之前灵奖,我們先來了解一下與方法相關(guān)的一些內(nèi)容。
SEL又叫選擇器估盘,是表示一個方法的selector的指針瓷患,其定義如下:
1typedefstructobjc_selector *SEL;
objc_selector結(jié)構(gòu)體的詳細(xì)定義沒有在<objc/runtime.h>頭文件中找到。方法的selector用于表示運行時方法的名字遣妥。Objective-C在編譯時擅编,會依據(jù)每一個方法的名字、參數(shù)序列箫踩,生成一個唯一的整型標(biāo)識(Int類型的地址)爱态,這個標(biāo)識就是SEL。如下代碼所示:
1
2
SEL sel1 =@selector(method1);
NSLog(@"sel : %p", sel1);
上面的輸出為:
12014-10-3018:40:07.518RuntimeTest[52734:466626] sel :0x100002d72
兩個類之間境钟,不管它們是父類與子類的關(guān)系锦担,還是之間沒有這種關(guān)系,只要方法名相同慨削,那么方法的SEL就是一樣的洞渔。每一個方法都對應(yīng)著一個SEL套媚。所以在Objective-C同一個類(及類的繼承體系)中,不能存在2個同名的方法磁椒,即使參數(shù)類型不同也不行堤瘤。相同的方法只能對應(yīng)一個SEL。這也就導(dǎo)致Objective-C在處理相同方法名且參數(shù)個數(shù)相同但類型不同的方法方面的能力很差浆熔。如在某個類中定義以下兩個方法:
1
2
- (void)setWidth:(int)width;
- (void)setWidth:(double)width;
這樣的定義被認(rèn)為是一種編譯錯誤本辐,所以我們不能像C++, C#那樣。而是需要像下面這樣來聲明:
1
2
-(void)setWidthIntValue:(int)width;
-(void)setWidthDoubleValue:(double)width;
當(dāng)然医增,不同的類可以擁有相同的selector慎皱,這個沒有問題。不同類的實例對象執(zhí)行相同的selector時调窍,會在各自的方法列表中去根據(jù)selector去尋找自己對應(yīng)的IMP宝冕。
工程中的所有的SEL組成一個Set集合,Set的特點就是唯一邓萨,因此SEL是唯一的地梨。因此,如果我們想到這個方法集合中查找某個方法時缔恳,只需要去找到這個方法對應(yīng)的SEL就行了宝剖,SEL實際上就是根據(jù)方法名hash化了的一個字符串,而對于字符串的比較僅僅需要比較他們的地址就可以了歉甚,可以說速度上無語倫比M蛳浮!但是纸泄,有一個問題赖钞,就是數(shù)量增多會增大hash沖突而導(dǎo)致的性能下降(或是沒有沖突,因為也可能用的是perfect hash)聘裁。但是不管使用什么樣的方法加速雪营,如果能夠?qū)⒖偭繙p少(多個方法可能對應(yīng)同一個SEL),那將是最犀利的方法衡便。那么献起,我們就不難理解,為什么SEL僅僅是函數(shù)名了镣陕。
本質(zhì)上谴餐,SEL只是一個指向方法的指針(準(zhǔn)確的說,只是一個根據(jù)方法名hash化了的KEY值呆抑,能唯一代表一個方法)岂嗓,它的存在只是為了加快方法的查詢速度。這個查找過程我們將在下面討論理肺。
我們可以在運行時添加新的selector摄闸,也可以在運行時獲取已存在的selector善镰,我們可以通過下面三種方法來獲取SEL:
sel_registerName函數(shù)
Objective-C編譯器提供的@selector()
NSSelectorFromString()方法
IMP實際上是一個函數(shù)指針妹萨,指向方法實現(xiàn)的首地址年枕。其定義如下:
1id(*IMP)(id, SEL, ...)
這個函數(shù)使用當(dāng)前CPU架構(gòu)實現(xiàn)的標(biāo)準(zhǔn)的C調(diào)用約定。第一個參數(shù)是指向self的指針(如果是實例方法乎完,則是類實例的內(nèi)存地址熏兄;如果是類方法,則是指向元類的指針)树姨,第二個參數(shù)是方法選擇器(selector)摩桶,接下來是方法的實際參數(shù)列表。
前面介紹過的SEL就是為了查找方法的最終實現(xiàn)IMP的帽揪。由于每個方法對應(yīng)唯一的SEL硝清,因此我們可以通過SEL方便快速準(zhǔn)確地獲得它所對應(yīng)的IMP,查找過程將在下面討論转晰。取得IMP后芦拿,我們就獲得了執(zhí)行這個方法代碼的入口點,此時查邢,我們就可以像調(diào)用普通的C語言函數(shù)一樣來使用這個函數(shù)指針了蔗崎。
通過取得IMP,我們可以跳過Runtime的消息傳遞機制扰藕,直接執(zhí)行IMP指向的函數(shù)實現(xiàn)缓苛,這樣省去了Runtime消息傳遞過程中所做的一系列查找操作,會比直接向?qū)ο蟀l(fā)送消息高效一些邓深。
介紹完SEL和IMP未桥,我們就可以來講講Method了。Method用于表示類定義中的方法芥备,則定義如下:
1
2
3
4
5
6
7
typedefstructobjc_method *Method;
structobjc_method {
SEL method_name? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;// 方法名
char*method_types? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
IMP method_imp? ? ? ? ? ? OBJC2_UNAVAILABLE;// 方法實現(xiàn)
}
我們可以看到該結(jié)構(gòu)體中包含一個SEL和IMP冬耿,實際上相當(dāng)于在SEL和IMP之間作了一個映射。有了SEL门躯,我們便可以找到對應(yīng)的IMP淆党,從而調(diào)用方法的實現(xiàn)代碼。具體操作流程我們將在下面討論讶凉。
objc_method_description定義了一個Objective-C方法染乌,其定義如下:
1structobjc_method_description { SEL name;char*types; };
Runtime提供了一系列的方法來處理與方法相關(guān)的操作。包括方法本身及SEL懂讯。本節(jié)我們介紹一下這些函數(shù)荷憋。
方法操作相關(guān)函數(shù)包括下以:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 調(diào)用指定方法的實現(xiàn)
idmethod_invoke (idreceiver, Method m, ... );
// 調(diào)用返回一個數(shù)據(jù)結(jié)構(gòu)的方法的實現(xiàn)
voidmethod_invoke_stret (idreceiver, Method m, ... );
// 獲取方法名
SEL method_getName ( Method m );
// 返回方法的實現(xiàn)
IMP method_getImplementation ( Method m );
// 獲取描述方法參數(shù)和返回值類型的字符串
constchar* method_getTypeEncoding ( Method m );
// 獲取方法的返回值類型的字符串
char* method_copyReturnType ( Method m );
// 獲取方法的指定位置參數(shù)的類型字符串
char* method_copyArgumentType ( Method m,unsignedintindex );
// 通過引用返回方法的返回值類型字符串
voidmethod_getReturnType ( Method m,char*dst, size_t dst_len );
// 返回方法的參數(shù)的個數(shù)
unsignedintmethod_getNumberOfArguments ( Method m );
// 通過引用返回方法指定位置參數(shù)的類型字符串
voidmethod_getArgumentType ( Method m,unsignedintindex,char*dst, size_t dst_len );
// 返回指定方法的方法描述結(jié)構(gòu)體
structobjc_method_description * method_getDescription ( Method m );
// 設(shè)置方法的實現(xiàn)
IMP method_setImplementation ( Method m, IMP imp );
// 交換兩個方法的實現(xiàn)
voidmethod_exchangeImplementations ( Method m1, Method m2 );
method_invoke函數(shù),返回的是實際實現(xiàn)的返回值褐望。參數(shù)receiver不能為空勒庄。這個方法的效率會比method_getImplementation和method_getName更快串前。
method_getName函數(shù),返回的是一個SEL实蔽。如果想獲取方法名的C字符串荡碾,可以使用sel_getName(method_getName(method))。
method_getReturnType函數(shù)局装,類型字符串會被拷貝到dst中坛吁。
method_setImplementation函數(shù),注意該函數(shù)返回值是方法之前的實現(xiàn)铐尚。
選擇器相關(guān)的操作函數(shù)包括:
1
2
3
4
5
6
7
8
9
10
11
// 返回給定選擇器指定的方法的名稱
constchar* sel_getName ( SEL sel );
// 在Objective-C Runtime系統(tǒng)中注冊一個方法拨脉,將方法名映射到一個選擇器,并返回這個選擇器
SEL sel_registerName (constchar*str );
// 在Objective-C Runtime系統(tǒng)中注冊一個方法
SEL sel_getUid (constchar*str );
// 比較兩個選擇器
BOOLsel_isEqual ( SEL lhs, SEL rhs );
sel_registerName函數(shù):在我們將一個方法添加到類定義時宣增,我們必須在Objective-C Runtime系統(tǒng)中注冊一個方法名以獲取方法的選擇器玫膀。
在Objective-C中,消息直到運行時才綁定到方法實現(xiàn)上爹脾。編譯器會將消息表達式[receiver message]轉(zhuǎn)化為一個消息函數(shù)的調(diào)用帖旨,即objc_msgSend。這個函數(shù)將消息接收者和方法名作為其基礎(chǔ)參數(shù)誉简,如以下所示:
1objc_msgSend(receiver, selector)
如果消息中還有其它參數(shù)碉就,則該方法的形式如下所示:
1objc_msgSend(receiver, selector, arg1, arg2, ...)
這個函數(shù)完成了動態(tài)綁定的所有事情:
首先它找到selector對應(yīng)的方法實現(xiàn)。因為同一個方法可能在不同的類中有不同的實現(xiàn)闷串,所以我們需要依賴于接收者的類來找到的確切的實現(xiàn)瓮钥。
它調(diào)用方法實現(xiàn),并將接收者對象及方法的所有參數(shù)傳給它烹吵。
最后碉熄,它將實現(xiàn)返回的值作為它自己的返回值。
消息的關(guān)鍵在于我們前面章節(jié)討論過的結(jié)構(gòu)體objc_class肋拔,這個結(jié)構(gòu)體有兩個字段是我們在分發(fā)消息的關(guān)注的:
指向父類的指針
一個類的方法分發(fā)表锈津,即methodLists。
當(dāng)我們創(chuàng)建一個新對象時凉蜂,先為其分配內(nèi)存琼梆,并初始化其成員變量。其中isa指針也會被初始化窿吩,讓對象可以訪問類及類的繼承體系茎杂。
下圖演示了這樣一個消息的基本框架:
當(dāng)消息發(fā)送給一個對象時,objc_msgSend通過對象的isa指針獲取到類的結(jié)構(gòu)體纫雁,然后在方法分發(fā)表里面查找方法的selector煌往。如果沒有找到selector,則通過objc_msgSend結(jié)構(gòu)體中的指向父類的指針找到其父類轧邪,并在父類的分發(fā)表里面查找方法的selector刽脖。依此羞海,會一直沿著類的繼承體系到達NSObject類。一旦定位到selector曲管,函數(shù)會就獲取到了實現(xiàn)的入口點却邓,并傳入相應(yīng)的參數(shù)來執(zhí)行方法的具體實現(xiàn)。如果最后沒有定位到selector翘地,則會走消息轉(zhuǎn)發(fā)流程申尤,這個我們在后面討論癌幕。
為了加速消息的處理衙耕,運行時系統(tǒng)緩存使用過的selector及對應(yīng)的方法的地址。這點我們在前面討論過勺远,不再重復(fù)橙喘。
objc_msgSend有兩個隱藏參數(shù):
消息接收對象
方法的selector
這兩個參數(shù)為方法的實現(xiàn)提供了調(diào)用者的信息。之所以說是隱藏的胶逢,是因為它們在定義方法的源代碼中沒有聲明厅瞎。它們是在編譯期被插入實現(xiàn)代碼的。
雖然這些參數(shù)沒有顯示聲明初坠,但在代碼中仍然可以引用它們和簸。我們可以使用self來引用接收者對象,使用_cmd來引用選擇器碟刺。如下代碼所示:
1
2
3
4
5
6
7
8
9
10
- strange
{
idtarget = getTheReceiver();
? ? SEL method = getTheMethod();
if( target ==self|| method == _cmd )
returnnil;
return[target performSelector:method];
}
當(dāng)然锁保,這兩個參數(shù)我們用得比較多的是self,_cmd在實際中用得比較少半沽。
Runtime中方法的動態(tài)綁定讓我們寫代碼時更具靈活性爽柒,如我們可以把消息轉(zhuǎn)發(fā)給我們想要的對象,或者隨意交換一個方法的實現(xiàn)等者填。不過靈活性的提升也帶來了性能上的一些損耗浩村。畢竟我們需要去查找方法的實現(xiàn),而不像函數(shù)調(diào)用來得那么直接占哟。當(dāng)然心墅,方法的緩存一定程度上解決了這一問題。
我們上面提到過榨乎,如果想要避開這種動態(tài)綁定方式怎燥,我們可以獲取方法實現(xiàn)的地址,然后像調(diào)用函數(shù)一樣來直接調(diào)用它谬哀。特別是當(dāng)我們需要在一個循環(huán)內(nèi)頻繁地調(diào)用一個特定的方法時刺覆,通過這種方式可以提高程序的性能。
NSObject類提供了methodForSelector:方法史煎,讓我們可以獲取到方法的指針谦屑,然后通過這個指針來調(diào)用實現(xiàn)代碼驳糯。我們需要將methodForSelector:返回的指針轉(zhuǎn)換為合適的函數(shù)類型,函數(shù)參數(shù)和返回值都需要匹配上氢橙。
我們通過以下代碼來看看methodForSelector:的使用:
1
2
3
4
5
6
7
void(*setter)(id, SEL,BOOL);
inti;
setter= (void(*)(id, SEL,BOOL))[target methodForSelector:@selector(setFilled:)];
for(i =0; i <1000; i++)
setter(targetList[i],@selector(setFilled:),YES);
這里需要注意的就是函數(shù)指針的前兩個參數(shù)必須是id和SEL酝枢。
當(dāng)然這種方式只適合于在類似于for循環(huán)這種情況下頻繁調(diào)用同一方法,以提高性能的情況悍手。另外帘睦,methodForSelector:是由Cocoa運行時提供的;它不是Objective-C語言的特性坦康。
當(dāng)一個對象能接收一個消息時竣付,就會走正常的方法調(diào)用流程。但如果一個對象無法接收指定消息時滞欠,又會發(fā)生什么事呢古胆?默認(rèn)情況下,如果是以[object message]的方式調(diào)用方法筛璧,如果object無法響應(yīng)message消息時逸绎,編譯器會報錯。但如果是以perform...的形式來調(diào)用夭谤,則需要等到運行時才能確定object是否能接收message消息棺牧。如果不能,則程序崩潰朗儒。
通常厢绝,當(dāng)我們不能確定一個對象是否能接收某個消息時避除,會先調(diào)用respondsToSelector:來判斷一下炼团。如下代碼所示:
1
2
3
if([selfrespondsToSelector:@selector(method)]) {
[selfperformSelector:@selector(method)];
}
不過来屠,我們這邊想討論下不使用respondsToSelector:判斷的情況。這才是我們這一節(jié)的重點榆鼠。
當(dāng)一個對象無法接收某一消息時纲爸,就會啟動所謂”消息轉(zhuǎn)發(fā)(message forwarding)“機制,通過這一機制妆够,我們可以告訴對象如何處理未知的消息识啦。默認(rèn)情況下,對象接收到未知的消息神妹,會導(dǎo)致程序崩潰颓哮,通過控制臺,我們可以看到以下異常信息:
1
2
3
-[SUTRuntimeMethod method]: unrecognized selector sent to instance 0x100111940
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[SUTRuntimeMethod method]: unrecognized selector sent to instance 0x100111940'
這段異常信息實際上是由NSObject的”doesNotRecognizeSelector“方法拋出的鸵荠。不過冕茅,我們可以采取一些措施,讓我們的程序執(zhí)行特定的邏輯,而避免程序的崩潰姨伤。
消息轉(zhuǎn)發(fā)機制基本上分為三個步驟:
動態(tài)方法解析
備用接收者
完整轉(zhuǎn)發(fā)
下面我們詳細(xì)討論一下這三個步驟哨坪。
對象在接收到未知的消息時,首先會調(diào)用所屬類的類方法+resolveInstanceMethod:(實例方法)或者+resolveClassMethod:(類方法)乍楚。在這個方法中当编,我們有機會為該未知消息新增一個”處理方法””。不過使用該方法的前提是我們已經(jīng)實現(xiàn)了該”處理方法”徒溪,只需要在運行時通過class_addMethod函數(shù)動態(tài)添加到類里面就可以了忿偷。如下代碼所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
voidfunctionForMethod1(idself, SEL _cmd) {
NSLog(@"%@, %p",self, _cmd);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString*selectorString =NSStringFromSelector(sel);
if([selectorString isEqualToString:@"method1"]) {
class_addMethod(self.class,@selector(method1), (IMP)functionForMethod1,"@:");
? ? }
return[superresolveInstanceMethod:sel];
}
不過這種方案更多的是為了實現(xiàn)@dynamic屬性。
如果在上一步無法處理消息臊泌,則Runtime會繼續(xù)調(diào)以下方法:
1- (id)forwardingTargetForSelector:(SEL)aSelector
如果一個對象實現(xiàn)了這個方法鲤桥,并返回一個非nil的結(jié)果,則這個對象會作為消息的新接收者缺虐,且消息會被分發(fā)到這個對象芜壁。當(dāng)然這個對象不能是self自身,否則就是出現(xiàn)無限循環(huán)高氮。當(dāng)然,如果我們沒有指定相應(yīng)的對象來處理aSelector顷牌,則應(yīng)該調(diào)用父類的實現(xiàn)來返回結(jié)果剪芍。
使用這個方法通常是在對象內(nèi)部,可能還有一系列其它對象能處理該消息窟蓝,我們便可借這些對象來處理消息并返回罪裹,這樣在對象外部看來,還是由該對象親自處理了這一消息运挫。如下代碼所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@interfaceSUTRuntimeMethodHelper:NSObject
- (void)method2;
@end
@implementationSUTRuntimeMethodHelper
- (void)method2 {
NSLog(@"%@, %p",self, _cmd);
}
@end
#pragma mark -
@interfaceSUTRuntimeMethod(){
? ? SUTRuntimeMethodHelper *_helper;
}
@end
@implementationSUTRuntimeMethod
+ (instancetype)object {
return[[selfalloc] init];
}
- (instancetype)init {
self= [superinit];
if(self!=nil) {
? ? ? ? _helper = [[SUTRuntimeMethodHelper alloc] init];
? ? }
returnself;
}
- (void)test {
[selfperformSelector:@selector(method2)];
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"forwardingTargetForSelector");
NSString*selectorString =NSStringFromSelector(aSelector);
// 將消息轉(zhuǎn)發(fā)給_helper來處理
if([selectorString isEqualToString:@"method2"]) {
return_helper;
? ? }
return[superforwardingTargetForSelector:aSelector];
}
@end
這一步合適于我們只想將消息轉(zhuǎn)發(fā)到另一個能處理該消息的對象上状共。但這一步無法對消息進行處理,如操作消息的參數(shù)和返回值谁帕。
如果在上一步還不能處理未知消息峡继,則唯一能做的就是啟用完整的消息轉(zhuǎn)發(fā)機制了。此時會調(diào)用以下方法:
1- (void)forwardInvocation:(NSInvocation*)anInvocation
運行時系統(tǒng)會在這一步給消息接收者最后一次機會將消息轉(zhuǎn)發(fā)給其它對象匈挖。對象會創(chuàng)建一個表示消息的NSInvocation對象碾牌,把與尚未處理的消息有關(guān)的全部細(xì)節(jié)都封裝在anInvocation中,包括selector儡循,目標(biāo)(target)和參數(shù)舶吗。我們可以在forwardInvocation方法中選擇將消息轉(zhuǎn)發(fā)給其它對象。
forwardInvocation:方法的實現(xiàn)有兩個任務(wù):
定位可以響應(yīng)封裝在anInvocation中的消息的對象择膝。這個對象不需要能處理所有未知消息誓琼。
使用anInvocation作為參數(shù),將消息發(fā)送到選中的對象。anInvocation將會保留調(diào)用結(jié)果腹侣,運行時系統(tǒng)會提取這一結(jié)果并將其發(fā)送到消息的原始發(fā)送者呵扛。
不過,在這個方法中我們可以實現(xiàn)一些更復(fù)雜的功能筐带,我們可以對消息的內(nèi)容進行修改今穿,比如追回一個參數(shù)等,然后再去觸發(fā)消息伦籍。另外蓝晒,若發(fā)現(xiàn)某個消息不應(yīng)由本類處理,則應(yīng)調(diào)用父類的同名方法帖鸦,以便繼承體系中的每個類都有機會處理此調(diào)用請求芝薇。
還有一個很重要的問題,我們必須重寫以下方法:
1- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
消息轉(zhuǎn)發(fā)機制使用從這個方法中獲取的信息來創(chuàng)建NSInvocation對象作儿。因此我們必須重寫這個方法洛二,為給定的selector提供一個合適的方法簽名。
完整的示例如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature*signature = [supermethodSignatureForSelector:aSelector];
if(!signature) {
if([SUTRuntimeMethodHelper instancesRespondToSelector:aSelector]) {
? ? ? ? ? ? signature = [SUTRuntimeMethodHelper instanceMethodSignatureForSelector:aSelector];
? ? ? ? }
? ? }
returnsignature;
}
- (void)forwardInvocation:(NSInvocation*)anInvocation {
if([SUTRuntimeMethodHelper instancesRespondToSelector:anInvocation.selector]) {
? ? ? ? [anInvocation invokeWithTarget:_helper];
? ? }
}
NSObject的forwardInvocation:方法實現(xiàn)只是簡單調(diào)用了doesNotRecognizeSelector:方法攻锰,它不會轉(zhuǎn)發(fā)任何消息晾嘶。這樣,如果不在以上所述的三個步驟中處理未知消息娶吞,則會引發(fā)一個異常垒迂。
從某種意義上來講,forwardInvocation:就像一個未知消息的分發(fā)中心妒蛇,將這些未知的消息轉(zhuǎn)發(fā)給其它對象机断。或者也可以像一個運輸站一樣將所有未知消息都發(fā)送給同一個接收對象绣夺。這取決于具體的實現(xiàn)吏奸。
回過頭來看第二和第三步,通過這兩個方法我們可以允許一個對象與其它對象建立關(guān)系陶耍,以處理某些未知消息奋蔚,而表面上看仍然是該對象在處理消息。通過這種關(guān)系物臂,我們可以模擬“多重繼承”的某些特性旺拉,讓對象可以“繼承”其它對象的特性來處理一些事情。不過棵磷,這兩者間有一個重要的區(qū)別:多重繼承將不同的功能集成到一個對象中蛾狗,它會讓對象變得過大,涉及的東西過多仪媒;而消息轉(zhuǎn)發(fā)將功能分解到獨立的小的對象中沉桌,并通過某種方式將這些對象連接起來谢鹊,并做相應(yīng)的消息轉(zhuǎn)發(fā)。
不過消息轉(zhuǎn)發(fā)雖然類似于繼承留凭,但NSObject的一些方法還是能區(qū)分兩者佃扼。如respondsToSelector:和isKindOfClass:只能用于繼承體系,而不能用于轉(zhuǎn)發(fā)鏈蔼夜。便如果我們想讓這種消息轉(zhuǎn)發(fā)看起來像是繼承兼耀,則可以重寫這些方法,如以下代碼所示:
1
2
3
4
5
6
7
8
9
10
11
12
- (BOOL)respondsToSelector:(SEL)aSelector
{
if( [superrespondsToSelector:aSelector])
returnYES;
else{
/* Here, test whether the aSelector message can? ? *
* be forwarded to another object and whether that? *
* object can respond to it. Return YES if it can.? */
}
returnNO;
}
在此求冷,我們已經(jīng)了解了Runtime中消息發(fā)送和轉(zhuǎn)發(fā)的基本機制瘤运。這也是Runtime的強大之處,通過它匠题,我們可以為程序增加很多動態(tài)的行為拯坟,雖然我們在實際開發(fā)中很少直接使用這些機制(如直接調(diào)用objc_msgSend),但了解它們有助于我們更多地去了解底層的實現(xiàn)韭山。其實在實際的編碼過程中郁季,我們也可以靈活地使用這些機制,去實現(xiàn)一些特殊的功能钱磅,如hook操作等梦裂。