OC的Runtime機(jī)制之動(dòng)態(tài)方法決議(Dynamic Method Resolution)

如果我們在 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ù)的返回值返回攻晒。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市班挖,隨后出現(xiàn)的幾起案子鲁捏,更是在濱河造成了極大的恐慌侣灶,老刑警劉巖避消,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異珍昨,居然都是意外死亡双揪,警方通過查閱死者的電腦和手機(jī)动羽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來渔期,“玉大人运吓,你說我怎么就攤上這事》杼耍” “怎么了拘哨?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長信峻。 經(jīng)常有香客問我宅静,道長,這世上最難降的妖魔是什么站欺? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任姨夹,我火速辦了婚禮,結(jié)果婚禮上矾策,老公的妹妹穿的比我還像新娘磷账。我一直安慰自己,他們只是感情好贾虽,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布逃糟。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪绰咽。 梳的紋絲不亂的頭發(fā)上菇肃,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天,我揣著相機(jī)與錄音取募,去河邊找鬼琐谤。 笑死,一個(gè)胖子當(dāng)著我的面吹牛玩敏,可吹牛的內(nèi)容都是我干的斗忌。 我是一名探鬼主播,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼旺聚,長吁一口氣:“原來是場噩夢啊……” “哼织阳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起砰粹,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤唧躲,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后碱璃,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惊窖,經(jīng)...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年厘贼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了界酒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,021評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡嘴秸,死狀恐怖毁欣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情岳掐,我是刑警寧澤凭疮,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站串述,受9級特大地震影響执解,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜纲酗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一衰腌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧觅赊,春花似錦右蕊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽帕翻。三九已至,卻和暖如春萝风,著一層夾襖步出監(jiān)牢的瞬間嘀掸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工规惰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留睬塌,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓卿拴,卻偏偏與公主長得像,于是被迫代替她去往敵國和親梨与。 傳聞我的和親對象是個(gè)殘疾皇子堕花,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評論 2 355

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