如果我們在 ObjectiveC 中向一個(gè)對象發(fā)送它無法處理的消息买喧,會(huì)出現(xiàn)什么情況呢?我們知道發(fā)送消息是通過 objc_send(id, SEL, ...) 來實(shí)現(xiàn)的,它會(huì)首 先在對象的類對象的 cache氓英,methodlist 以及父類對象的 cache,methodlist 中依次查找 SEL 對應(yīng) 的 IMP;如果沒有找到且實(shí)現(xiàn)了動(dòng)態(tài)方法決議機(jī)制就會(huì)進(jìn)行決議严沥,如果沒有實(shí)現(xiàn)動(dòng)態(tài)方法決議機(jī)制或決議 失敗且實(shí)現(xiàn)了消息轉(zhuǎn)發(fā)機(jī)制就會(huì)進(jìn)入消息轉(zhuǎn)發(fā)流程缺猛,否則程序 crash。也就是說如果同時(shí)提供了動(dòng)態(tài)方法 決議和消息轉(zhuǎn)發(fā)延届,那么動(dòng)態(tài)方法決議先于消息轉(zhuǎn)發(fā)剪勿,只有當(dāng)動(dòng)態(tài)方法決議依然無法正確決議 selector 的 實(shí)現(xiàn),才會(huì)嘗試進(jìn)行消息轉(zhuǎn)發(fā)方庭。在前文中厕吉,我并沒有詳細(xì)講解動(dòng)態(tài)方法決議,因此本文將詳細(xì)介紹之械念。
動(dòng)態(tài)方法決議
我們先定義一個(gè)類:
Objective C 提供了一種名為動(dòng)態(tài)方法決議的手段头朱,使得我們可以在運(yùn)行時(shí)動(dòng)態(tài)地為一個(gè) selector 提供 實(shí)現(xiàn)。我們只要實(shí)現(xiàn) +resolveInstanceMethod: 和/或 +resolveClassMethod: 方法龄减,并在其中為指 定的 selector 提供實(shí)現(xiàn)即可(通過調(diào)用運(yùn)行時(shí)函數(shù) class_addMethod 來添加)项钮。這兩個(gè)方法都是 NSObject 中的類方法,其原型為:
- (BOOL)resolveClassMethod:(SEL)name; + (BOOL)resolveInstanceMethod:(SEL)name;
參數(shù) name 是需要被動(dòng)態(tài)決議的 selector ,返回值文檔中說是表示動(dòng)態(tài)決議成功與否希停。但在上面的例子 中(不涉及消息轉(zhuǎn)發(fā)的情況下)烁巫,如果在該函數(shù)內(nèi)為指定的 selector 提供實(shí)現(xiàn),無論返回 YES 還是 NO宠能, 編譯運(yùn)行都是正確的;但如果在該函數(shù)內(nèi)并不真正為 selector 提供實(shí)現(xiàn)亚隙,無論返回 YES 還是 NO,運(yùn) 行都會(huì) crash违崇,道理很簡單阿弃,selector 并沒有對應(yīng)的實(shí)現(xiàn),而又沒有實(shí)現(xiàn)消息轉(zhuǎn)發(fā)亦歉。 resolveInstanceMethod 是為對象方法進(jìn)行決議恤浪,而 resolveClassMethod 是為類方法進(jìn)行決議畅哑。
@interface Foo : NSObject
-(void)MissMethod;
- (void)Bar;
@end
#import "Foo.h"
void dynamicMethodIMP(id self, SEL _cmd) {
NSLog(@" >> dynamicMethodIMP");
}
@implementation Foo
+ (BOOL)resolveInstanceMethod:(SEL)name {
NSLog(@" >> Instance resolving %@", NSStringFromSelector(name));
if (name == @selector(MissMethod)) {
class_addMethod([self class], name, (IMP)dynamicMethodIMP, "v@:"); return YES;
}
return [super resolveInstanceMethod:name];
}
+ (BOOL)resolveClassMethod:(SEL)name {
NSLog(@" >> Class resolving %@", NSStringFromSelector(name));
return [super resolveClassMethod:name];
}
- (void)Bar {
NSLog(@" >> Bar() in Foo");
}
//- (void)MissMethod {
//
//}
@end
然后調(diào)用
#import "ViewController.h"
#import "Foo.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Foo *foo = [[Foo alloc] init];
[foo Bar];
[foo MissMethod];
}
打印的信息:
**2017-07-02 15:58:53.578584+0800 DynamicMethodDemo[3147:601367] >> Bar() in Foo**
**2017-07-02 15:58:53.578782+0800 DynamicMethodDemo[3147:601367] >> Instance resolving MissMethod**
**2017-07-02 15:58:53.579087+0800 DynamicMethodDemo[3147:601367] >> dynamicMethodIMP**
如果將 [圖片上傳失敗...(image-f485ae-1522657310056)]
//class_addMethod([self class], name, (IMP)dynamicMethod IMP, "v@:");
這行注釋掉肴楷,雖然會(huì)返回YES,但是還是會(huì)崩潰,在這里荠呐,resolveInstanceMethod 使詐了赛蔫,它聲稱成功(返回 YES)決議了 selector砂客,但是并沒有真正 提供實(shí)現(xiàn),被編譯器發(fā)覺而提示相應(yīng)的錯(cuò)誤信息呵恢。那它的返回值到底有什么作用呢鞠值,在它沒有提供真正的 實(shí)現(xiàn),并且提供了消息轉(zhuǎn)發(fā)機(jī)制的情況下渗钉,YES 表示不進(jìn)行后續(xù)的消息轉(zhuǎn)發(fā)彤恶,返回 NO 則表示要進(jìn)行后 續(xù)的消息轉(zhuǎn)發(fā)。
動(dòng)態(tài)方法的內(nèi)部實(shí)現(xiàn)
1鳄橘,首先判斷是否實(shí)現(xiàn)了 resolveInstanceMethod声离,如果沒有實(shí)現(xiàn),返回 NULL瘫怜,進(jìn)入下一步處理; 2术徊,如果實(shí)現(xiàn)了,調(diào)用 resolveInstanceMethod鲸湃,獲取返回值;
3赠涮,如果返回值為 YES,表示 resolveInstanceMethod 聲稱它已經(jīng)提供了 selector 的實(shí)現(xiàn)暗挑,因此再次 查找 methodlist笋除,如果依然找到對應(yīng)的 IMP,則返回該實(shí)現(xiàn)窿祥,否則提示警告信息株憾,返回 NULL,進(jìn)入下 一步處理;
4晒衩,如果返回值為 NO嗤瞎,返回 NULL,進(jìn)入下一步處理;
加入消息轉(zhuǎn)發(fā)內(nèi)部走 _objc_msgForward
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL name = [anInvocation selector];
NSLog(@" >> forwardInvocation for selector %@", NSStringFromSelector(name));
Proxy * proxy = [[Proxy alloc] init];
if ([proxy respondsToSelector:name]) {
[anInvocation invokeWithTarget:proxy];
}
else {
[super forwardInvocation:anInvocation];
}
}
//methodSignatureForSelector:的作用在于為另一個(gè)類實(shí)現(xiàn)的消息創(chuàng)建一個(gè)有效的方法簽名听系,必須實(shí)現(xiàn)贝奇,并且返回不為空的methodSignature,否則會(huì)crash靠胜。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { return [Proxy instanceMethodSignatureForSelector:aSelector];
}
總結(jié):
從上面的示例演示可以看出掉瞳,動(dòng)態(tài)方法決議是先于消息轉(zhuǎn)發(fā)的。
如果向一個(gè) Objective C 對象對象發(fā)送它無法處理的消息(selector)浪漠,那么編譯器會(huì)按照如下次序進(jìn) 行處理:
1陕习,首先看是否為該 selector 提供了動(dòng)態(tài)方法決議機(jī)制,如果提供了則轉(zhuǎn)到 2;如果沒有提供則轉(zhuǎn)到 3; 2址愿,如果動(dòng)態(tài)方法決議真正為該 selector 提供了實(shí)現(xiàn)该镣,那么就調(diào)用該實(shí)現(xiàn),完成消息發(fā)送流程响谓,消息轉(zhuǎn)發(fā)
就不會(huì)進(jìn)行了;如果沒有提供损合,則轉(zhuǎn)到 3;
3省艳,其次看是否為該 selector 提供了消息轉(zhuǎn)發(fā)機(jī)制,如果提供了消息了則進(jìn)行消息轉(zhuǎn)發(fā)嫁审,此時(shí)跋炕,無論消息 轉(zhuǎn)發(fā)是怎樣實(shí)現(xiàn)的,程序均不會(huì) crash律适。(因?yàn)橄⒄{(diào)用的控制權(quán)完全交給消息轉(zhuǎn)發(fā)機(jī)制處理辐烂,即使消息
轉(zhuǎn)發(fā)并沒有做任何事情,運(yùn)行也不會(huì)有錯(cuò)誤捂贿,編譯器更不會(huì)有錯(cuò)誤提示棉圈。);如果沒提供消息轉(zhuǎn)發(fā)機(jī)制, 則轉(zhuǎn)到 4;
4眷蜓,運(yùn)行報(bào)錯(cuò):無法識(shí)別的 selector分瘾,程序 crash;
則到現(xiàn)在為止,我們已經(jīng)了解了消息的全部原理吁系,在這里再整理總結(jié)一遍
1.當(dāng)實(shí)例對象調(diào)用方法的時(shí)候德召,將方法轉(zhuǎn)換成 id objc_msgSend(id theReceiver, SELtheSelector, ...) (該消息做了動(dòng)態(tài)綁定的所需要的一切工作)
2.首先根據(jù)SEL去該類的方法 cache 中查找,如果找到了調(diào)到6;
3.如果沒有找到汽纤,就去該類的方法列表中查找上岗。如果在該類的方法列表中找到了,跳到6蕴坪,并將 它加入 cache 中緩存起來肴掷。根據(jù)最近使用原則,這個(gè)方法再次調(diào)用的可能性很大背传,緩存起來可以節(jié)省下次 調(diào)用再次查找的開銷呆瞻。
4.如果在該類的方法列表中沒找到對應(yīng)的 IMP,在通過該類結(jié)構(gòu)中的 super_class 指針在其父類結(jié)構(gòu)的方法列表中去查找径玖,直到在某個(gè)父類的方法列表中找到對應(yīng)的 IMP痴脾,跳轉(zhuǎn)到6,并加入 cache 中;
5.如果在自身以及所有父類的方法列表中都沒有找到對應(yīng)的 IMP梳星,首先看是否為該 selector 提供了動(dòng)態(tài)方法決議機(jī)制赞赖,如果動(dòng)態(tài)方法決議真正為該 selector 提供了實(shí)現(xiàn),那么就調(diào)用該實(shí)現(xiàn)冤灾,完成消息發(fā)送流程前域,消息轉(zhuǎn)發(fā) ,其次看是否為該 selector 提供了消息轉(zhuǎn)發(fā)機(jī)制韵吨,如果提供了消息了則進(jìn)行消息轉(zhuǎn)發(fā)匿垄,此時(shí),無論消息 轉(zhuǎn)發(fā)是怎樣實(shí)現(xiàn)的,程序均不會(huì) crash年堆。(因?yàn)橄⒄{(diào)用的控制權(quán)完全交給消息轉(zhuǎn)發(fā)機(jī)制處理,即使消息 轉(zhuǎn)發(fā)并沒有做任何事情盏浇,運(yùn)行也不會(huì)有錯(cuò)誤变丧,編譯器更不會(huì)有錯(cuò)誤提示
6.它首先找到 SEL 對應(yīng)的方法實(shí)現(xiàn) IMP。因?yàn)椴煌念悓ν环椒赡軙?huì)有不同的實(shí)現(xiàn)绢掰,所以找到的 方法實(shí)現(xiàn)依賴于消息接收者的類型痒蓬。
7.然后將消息接收者對象(指向消息接收者對象的指針)以及方法中指定的參數(shù)傳遞給方法實(shí)現(xiàn) IMP。
8.最后滴劲,將方法實(shí)現(xiàn)的返回值作為該函數(shù)的返回值返回攻晒。