1仑濒、方法method和selector(選擇子)有什么關(guān)系
在 Objective-C 中,selector偷遗,Method 和 implementation(IMP) 都是 Runtime 的組成部分墩瞳。在實(shí)際開(kāi)發(fā)中它們常常是可以相互轉(zhuǎn)換來(lái)處理消息的發(fā)送的。選擇子代表方法在 Runtime 期間的標(biāo)識(shí)符氏豌。為 SEL 類型喉酌,雖然 SEL 是 objc_selector 結(jié)構(gòu)體指針,但實(shí)際上它只是一個(gè) C 字符串泵喘。在類加載的時(shí)候泪电,編譯器會(huì)生成與方法相對(duì)應(yīng)的選擇子,并注冊(cè)到 Objective-C 的 Runtime 運(yùn)行系統(tǒng)涣旨。
得出結(jié)論:選擇子其實(shí)是方法的名稱歪架,不同類中方法名相同參數(shù)不同的倆個(gè)方法,他們的選擇子是相同的霹陡。
Method的結(jié)構(gòu)體
/// Method
struct objc_method {
SEL method_name;
char *method_types;
IMP method_imp;
};
- 方法名 method_name 類型為 SEL和蚪,前面提到過(guò)相同名字的方法即使在不同類中定義止状,它們的方法選擇器也相同。
- 方法類型 method_types 是個(gè) char 指針攒霹,其實(shí)存儲(chǔ)著方法的參數(shù)類型和返回值類型怯疤,即是 Type Encoding 編碼。(即類型編碼)
- method_imp 指向方法的實(shí)現(xiàn)催束,本質(zhì)上是一個(gè)函數(shù)的指針集峦,就是前面講到的 Implementation。
消息傳遞
在OC中抠刺,給對(duì)象發(fā)送消息的語(yǔ)法是:
id returnValue = [someObject messageName:parameter];
這里塔淤,someObject叫做
接收者(receiver)
,messageName:叫做選擇子(selector)
,選擇子和參數(shù)合起來(lái)稱為“消息”速妖。編譯器看到此消息后高蜂,將其轉(zhuǎn)換為一條標(biāo)準(zhǔn)的C語(yǔ)言函數(shù)調(diào)用,所調(diào)用的函數(shù)乃是消息傳遞機(jī)制中的核心函數(shù)叫做objc_msgSend罕容,它的原型如下:
void objc_msgSend(id self, SEL cmd, ...)
第一個(gè)參數(shù)代表接收者备恤,第二個(gè)參數(shù)代表選擇子,后續(xù)參數(shù)就是消息中的那些參數(shù)锦秒,數(shù)量是可變的·露泊,所以這個(gè)函數(shù)就是參數(shù)個(gè)數(shù)可變的函數(shù)。
因此旅择,上述以O(shè)C形式展現(xiàn)出來(lái)的函數(shù)就會(huì)轉(zhuǎn)化成如下函數(shù):
id returnValue = objc_msgSend(someObject,@selector(messageName:),parameter);
可以看出惭笑,在調(diào)用方法時(shí),編譯器將它轉(zhuǎn)成了objc_msgSend
消息發(fā)送了砌左,在Runtime的執(zhí)行過(guò)程如下
- 1脖咐、Runtime先通過(guò)對(duì)象
someobject
找到isa
指針铺敌,判斷isa指針是否為nil
汇歹,為nil
直接return。 - 2偿凭、若不為空則通過(guò)
isa
指針找到當(dāng)前實(shí)例的類對(duì)象产弹,在類對(duì)象下查找緩存是否有messageName
方法。 - 3弯囊、若在類對(duì)象緩存中找到
messageName
方法痰哨,則直接調(diào)用IMP
方法(本質(zhì)上是函數(shù)的指針)。 - 4匾嘱、若在類對(duì)象緩存中沒(méi)找到
messageName
方法斤斧,則查找當(dāng)前類對(duì)象的方法列表methodlist
,若找到方法則將其添加到類對(duì)象的緩存中霎烙。 - 5撬讽、若在類對(duì)象方法列表中沒(méi)找到
messageName
方法蕊连,則繼續(xù)到當(dāng)前類的父類中以相同的方式查找(即類的緩存->類的方法列表)。 - 6游昼、若在父類中找到
messageName
方法甘苍,則將IMP
添加到類對(duì)象緩存中。 - 7烘豌、若在父類中沒(méi)找到
messageName
方法载庭,則繼續(xù)查詢父類的父類,直到追溯到最上層NSObject
廊佩。 - 8囚聚、若還是沒(méi)有找到,則啟用動(dòng)態(tài)方法解析标锄、備用接收者靡挥、消息轉(zhuǎn)發(fā)三部曲,給程序最后一個(gè)機(jī)會(huì)
- 9鸯绿、若還是沒(méi)找到跋破,則Runtime會(huì)拋出異常
doesNotRecognizeSelector
。
綜上瓶蝴,方法的查詢流程基本就是查詢類對(duì)象中的緩存和方法列表->父類中的緩存和方法列表->父類的父類中的緩存和方法列表->...->NSObject中的緩存和方法列表->動(dòng)態(tài)方法解析->備用接收者->消息轉(zhuǎn)發(fā)毒返。
消息動(dòng)態(tài)解析
Objective-C 運(yùn)行時(shí)會(huì)調(diào)用 +resolveInstanceMethod:
或者 +resolveClassMethod:
,讓你有機(jī)會(huì)提供一個(gè)函數(shù)實(shí)現(xiàn)舷手。前者在 對(duì)象方法未找到時(shí) 調(diào)用拧簸,后者在 類方法未找到時(shí) 調(diào)用。我們可以通過(guò)重寫(xiě)這兩個(gè)方法男窟,添加其他函數(shù)實(shí)現(xiàn)盆赤,并返回 YES
, 那運(yùn)行時(shí)系統(tǒng)就會(huì)重新啟動(dòng)一次消息發(fā)送的過(guò)程歉眷。
主要用的的方法如下:
// 類方法未找到時(shí)調(diào)起牺六,可以在此添加方法實(shí)現(xiàn)
+ (BOOL)resolveClassMethod:(SEL)sel;
// 對(duì)象方法未找到時(shí)調(diào)起,可以在此添加方法實(shí)現(xiàn)
+ (BOOL)resolveInstanceMethod:(SEL)sel;
/**
* class_addMethod 向具有給定名稱和實(shí)現(xiàn)的類中添加新方法
* @param cls 被添加方法的類
* @param name selector 方法名
* @param imp 實(shí)現(xiàn)方法的函數(shù)指針
* @param types imp 指向函數(shù)的返回值與參數(shù)類型
* @return 如果添加方法成功返回 YES汗捡,否則返回 NO
*/
BOOL class_addMethod(Class cls, SEL name, IMP imp,
const char * _Nullable types);
測(cè)試代碼
main.m 文件中
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
Person *xiaoming = [[Person alloc]init];
[xiaoming performSelector:@selector(way)];
return 0;
}
person.m 文件中
// 重寫(xiě) resolveInstanceMethod: 添加對(duì)象方法實(shí)現(xiàn)
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(way)) {
class_addMethod([self class], sel,class_getMethodImplementation([self class], @selector(method)), "123");//使用class_addMethod動(dòng)態(tài)添加方法method
}
return YES;
}
- (void)method{
NSLog(@"進(jìn)入了消息動(dòng)態(tài)解析");
}
運(yùn)行了上述的測(cè)試代碼后淑际,我們會(huì)發(fā)現(xiàn)即便我們并沒(méi)有實(shí)現(xiàn)way方法,而且使用了performSelector去強(qiáng)行調(diào)用way方法扇住,但是我們的程序并沒(méi)有崩潰春缕,是因?yàn)樵诓檎伊祟惙椒ê退械母割惡筮€是沒(méi)有找到way方法,程序進(jìn)入了消息動(dòng)態(tài)解析艘蹋,然后我們使用了class_addMethod去動(dòng)態(tài)添加方法method锄贼,最后程序從調(diào)用
performSelector和直接調(diào)用方法的區(qū)別
performSelector: withObject:
是在iOS中的一種方法調(diào)用方式。他可以向一個(gè)對(duì)象傳遞任何消息女阀,而不需要在編譯的時(shí)候聲明這些方法宅荤。所以這也是runtime
的一種應(yīng)用方式米间。
所以performSelector
和直接調(diào)用方法的區(qū)別就在與runtime
。直接調(diào)用編譯是會(huì)自動(dòng)校驗(yàn)膘侮。如果方法不存在屈糊,那么直接調(diào)用 在編譯時(shí)候就能夠發(fā)現(xiàn),編譯器會(huì)直接報(bào)錯(cuò)琼了。
但是使用performSelector
的話一定是在運(yùn)行時(shí)候才能發(fā)現(xiàn)逻锐,如果此方法不存在就會(huì)崩潰。所以一般使用performSelector
的時(shí)候雕薪,一般都會(huì)使用- (BOOL)respondsToSelector:(SEL)aSelector;
來(lái)在運(yùn)行時(shí)判斷對(duì)象是否響應(yīng)此方法昧诱。
消息接受者重定向(備用接受者)
如果上一步中 +resolveInstanceMethod:
或者 +resolveClassMethod:
沒(méi)有添加其他函數(shù)實(shí)現(xiàn),運(yùn)行時(shí)就會(huì)進(jìn)行下一步:消息接受者重定向所袁。
如果當(dāng)前對(duì)象實(shí)現(xiàn)了 -forwardingTargetForSelector:
或者 +forwardingTargetForSelector:
方法盏档,Runtime 就會(huì)調(diào)用這個(gè)方法,允許我們將消息的接受者轉(zhuǎn)發(fā)給其他對(duì)象燥爷。
其中用到的方法蜈亩。
// 重定向類方法的消息接收者,返回一個(gè)類或?qū)嵗龑?duì)象
+ (id)forwardingTargetForSelector:(SEL)aSelector;
// 重定向方法的消息接收者前翎,返回一個(gè)類或?qū)嵗龑?duì)象
- (id)forwardingTargetForSelector:(SEL)aSelector;
注意:
- 類方法和對(duì)象方法消息轉(zhuǎn)發(fā)第二步調(diào)用的方法不一樣稚配,前者是
+forwardingTargetForSelector:
方法,后者是-forwardingTargetForSelector:
方法港华。- 這里
+resolveInstanceMethod:
或者+resolveClassMethod:
無(wú)論是返回YES
道川,還是返回NO
,只要其中沒(méi)有添加其他函數(shù)實(shí)現(xiàn)立宜,運(yùn)行時(shí)都會(huì)進(jìn)行下一步冒萄。
測(cè)試代碼
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(way)) {
Friends *friends = [[Friends alloc]init];
return friends;//返回friends對(duì)象,讓friends對(duì)象接受這個(gè)消息
}
return [super forwardingTargetForSelector:aSelector];
}
可以看到橙数,雖然當(dāng)前 person 沒(méi)有實(shí)現(xiàn) fun
方法尊流,+resolveInstanceMethod:
也沒(méi)有添加其他函數(shù)實(shí)現(xiàn)。但是我們通過(guò) forwardingTargetForSelector
把當(dāng)前 person
的方法轉(zhuǎn)發(fā)給了 friends
對(duì)象去執(zhí)行了商模。打印結(jié)果也證明我們成功實(shí)現(xiàn)了轉(zhuǎn)發(fā)奠旺。
我們通過(guò) forwardingTargetForSelector
可以修改消息的接收者蜘澜,該方法返回參數(shù)是一個(gè)對(duì)象施流,如果這個(gè)對(duì)象是不是 nil
,也不是 self
鄙信,系統(tǒng)會(huì)將運(yùn)行的消息轉(zhuǎn)發(fā)給這個(gè)對(duì)象執(zhí)行瞪醋。否則,繼續(xù)進(jìn)行下一步:消息重定向流程装诡。
消息重定向
如果經(jīng)過(guò)消息動(dòng)態(tài)解析银受、消息接受者重定向践盼,Runtime 系統(tǒng)還是找不到相應(yīng)的方法實(shí)現(xiàn)而無(wú)法響應(yīng)消息,Runtime 系統(tǒng)會(huì)利用 -methodSignatureForSelector:
或者 +methodSignatureForSelector:
方法獲取函數(shù)的參數(shù)和返回值類型宾巍。
- 如果
methodSignatureForSelector:
返回了一個(gè)NSMethodSignature
對(duì)象(函數(shù)簽名)咕幻,Runtime 系統(tǒng)就會(huì)創(chuàng)建一個(gè)NSInvocation
對(duì)象,并通過(guò)forwardInvocation:
消息通知當(dāng)前對(duì)象顶霞,給予此次消息發(fā)送最后一次尋找 IMP 的機(jī)會(huì)肄程。 - 如果
methodSignatureForSelector:
返回nil
。則 Runtime 系統(tǒng)會(huì)發(fā)出doesNotRecognizeSelector:
消息选浑,程序也就崩潰了蓝厌。
所以我們可以在 forwardInvocation:
方法中對(duì)消息進(jìn)行轉(zhuǎn)發(fā)。
注意:類方法和對(duì)象方法消息轉(zhuǎn)發(fā)第三步調(diào)用的方法同樣不一樣古徒。
類方法調(diào)用的是:
+ methodSignatureForSelector:
+ forwardInvocation:
+ doesNotRecognizeSelector:
對(duì)象方法調(diào)用的是:
- methodSignatureForSelector:
- forwardInvocation:
- doesNotRecognizeSelector:
用到的方法:
// 獲取類方法函數(shù)的參數(shù)和返回值類型拓提,返回簽名
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
// 類方法消息重定向
+ (void)forwardInvocation:(NSInvocation *)anInvocation;
// 獲取對(duì)象方法函數(shù)的參數(shù)和返回值類型隧膘,返回簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
// 對(duì)象方法消息重定向
- (void)forwardInvocation:(NSInvocation *)anInvocation代态;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(way)) {
return [NSMethodSignature methodSignatureForSelector:@selector(way)];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
SEL sel = anInvocation.selector;
Friends *f = [[Friends alloc] init];
if([f respondsToSelector:sel]) { // 判斷 Person 對(duì)象方法是否可以響應(yīng) sel
[anInvocation invokeWithTarget:f]; // 若可以響應(yīng),則將消息轉(zhuǎn)發(fā)給其他對(duì)象處理
} else {
[self doesNotRecognizeSelector:sel]; // 若仍然無(wú)法響應(yīng)疹吃,則報(bào)錯(cuò):找不到響應(yīng)方法
}
}
既然 -forwardingTargetForSelector:
和 -forwardInvocation:
都可以將消息轉(zhuǎn)發(fā)給其他對(duì)象處理胆数,那么兩者的區(qū)別在哪?
區(qū)別就在于 -forwardingTargetForSelector:
只能將消息轉(zhuǎn)發(fā)給一個(gè)對(duì)象互墓。而 -forwardInvocation:
可以將消息轉(zhuǎn)發(fā)給多個(gè)對(duì)象必尼。
以上就是 Runtime 消息轉(zhuǎn)發(fā)的整個(gè)流程。
消息發(fā)送以及轉(zhuǎn)發(fā)機(jī)制總結(jié)
調(diào)用 [receiver selector];
后篡撵,進(jìn)行的流程:
-
編譯階段:
[receiver selector];
方法被編譯器轉(zhuǎn)換為:-
objc_msgSend(receiver判莉,selector)
(不帶參數(shù)) -
objc_msgSend(recevier,selector育谬,org1券盅,org2,…)
(帶參數(shù))
-
-
運(yùn)行時(shí)階段:消息接受者
recevier
尋找對(duì)應(yīng)的selector
- 通過(guò)
recevier
的isa 指針
找到recevier
的class(類)
膛檀; - 在
Class(類)
的cache(方法緩存)
的散列表中尋找對(duì)應(yīng)的IMP(方法實(shí)現(xiàn))
锰镀; - 如果在
cache(方法緩存)
中沒(méi)有找到對(duì)應(yīng)的IMP(方法實(shí)現(xiàn))
的話,就繼續(xù)在Class(類)
的method list(方法列表)
中找對(duì)應(yīng)的selector
咖刃,如果找到泳炉,填充到cache(方法緩存)
中,并返回selector
嚎杨; - 如果在
class(類)
中沒(méi)有找到這個(gè)selector
花鹅,就繼續(xù)在它的superclass(父類)
中尋找; - 一旦找到對(duì)應(yīng)的
selector
枫浙,直接執(zhí)行recevier
對(duì)應(yīng)selector
方法實(shí)現(xiàn)的IMP(方法實(shí)現(xiàn))
刨肃。 - 若找不到對(duì)應(yīng)的
selector
古拴,Runtime 系統(tǒng)進(jìn)入消息轉(zhuǎn)發(fā)機(jī)制。
- 通過(guò)
-
運(yùn)行時(shí)消息轉(zhuǎn)發(fā)階段:
- 動(dòng)態(tài)解析:通過(guò)重寫(xiě)
+resolveInstanceMethod:
或者+resolveClassMethod:
方法真友,利用class_addMethod
方法添加其他函數(shù)實(shí)現(xiàn)黄痪; - 消息接受者重定向:如果上一步添加其他函數(shù)實(shí)現(xiàn),可在當(dāng)前對(duì)象中利用
forwardingTargetForSelector:
方法將消息的接受者轉(zhuǎn)發(fā)給其他對(duì)象盔然; - 消息重定向:如果上一步?jīng)]有返回值為
nil
满力,則利用methodSignatureForSelector:
方法獲取函數(shù)的參數(shù)和返回值類型。- 如果
methodSignatureForSelector:
返回了一個(gè)NSMethodSignature
對(duì)象(函數(shù)簽名)轻纪,Runtime 系統(tǒng)就會(huì)創(chuàng)建一個(gè)NSInvocation
對(duì)象油额,并通過(guò)forwardInvocation:
消息通知當(dāng)前對(duì)象,給予此次消息發(fā)送最后一次尋找 IMP 的機(jī)會(huì)刻帚。 - 如果
methodSignatureForSelector:
返回nil
潦嘶。則 Runtime 系統(tǒng)會(huì)發(fā)出doesNotRecognizeSelector:
消息,程序也就崩潰了崇众。
- 如果
- 動(dòng)態(tài)解析:通過(guò)重寫(xiě)