iOS 消息轉發(fā)機制

(一)對象的消息傳遞機制 objc_msgSend()

這叫做“給某個對象發(fā)送某條消息”扑庞。消息有“名稱”或“選擇子(selector)”之說。消息可以接受參數(shù),而且還可以有返回值。

id returnValue = [someObject messgeName:parameter];

本例中,someObject叫做方法調用者守屉,也叫做接受者(receiver)。messageName:是方法名蒿辙,也叫做選擇子(selector)拇泛。選擇子與參數(shù)合起來叫做”消息“(message)。在運行時思灌,編譯器會把上面這個格式的方法調用轉化為一條標準的C語言函數(shù)調用俺叭,該函數(shù)就是鼎鼎有名的objc_msgSend(),該函數(shù)是消息objc里在運行時傳遞機制中的核心函數(shù)泰偿,其原型如下:

void objc_msgSend(id self, SEL cmd, ...)
多個參數(shù)按照順序排列

根據(jù)原型轉換后:

id returnValue = objc_msgSend(someObject,  @selector(messageName:), parameter)

執(zhí)行這條語句的時候熄守,向一個對象發(fā)送消息時,首先會在對象類的cache,method list以及父類對象的cache,method list依次查找SEL對應的IMP
(cache文末有解釋-方法緩存)

如果沒有找到耗跛,并且實現(xiàn)了動態(tài)方法決議機制就會決議裕照。如果沒有實現(xiàn)動態(tài)決議機制或者決議失敗且實現(xiàn)了消息轉發(fā)機制。就會進入消息轉發(fā)流程调塌。否則程序Crash.

消息具體傳遞流程:

objc_msgSend()函數(shù)會依據(jù)接受者(調用方法的對象)的類型和選擇子(方法名)來調用適當?shù)姆椒ā?/p>

  • 接收者會根據(jù)isa指針找到接收者自己所屬的類晋南,然后在所屬類的”方法列表“(method list)中從上向下遍歷。如果能找到與選擇子名稱相符的方法羔砾,就根據(jù)IMP指針跳轉到方法的實現(xiàn)代碼负间,調用這個方法的實現(xiàn)紊扬。
  • 如果找不到與選擇子名稱相符的方法,接收者會根據(jù)所屬類的superClass指針唉擂,沿著類的繼承體系繼續(xù)向上查找(向父類查找),如果能找到與名稱相符的方法檀葛,就根據(jù)IMP指針跳轉到方法的實現(xiàn)代碼玩祟,調用這個方法的實現(xiàn)。
  • 如果在繼承體系中還是找不到與選擇子相符的方法屿聋,此時就會執(zhí)行”消息轉發(fā)(message forwarding)“操作空扎。

(二)消息轉發(fā)流程

消息轉發(fā)分為兩個階段。第一階段叫做“動態(tài)方法解析(dynamic method resolution)”润讥,或者叫“動態(tài)方法決議”转锈。第二階段涉及到“完整的消息轉發(fā)機制(full forwarding mechanism)”,或者叫“完整的消息轉發(fā)原理”楚殿。

  • 動態(tài)方法決議

字面上我的理解是:消息在發(fā)送過程中進行判斷到底這消息該由誰接收

一:詢問是否有動態(tài)添加方法來進行處理
Objective C 提供了一種名為動態(tài)方法決議的手段撮慨,使得我們可以在運行時動態(tài)地為一個 selector 提供實現(xiàn)。我們只要實現(xiàn)

+ (BOOL)resolveInstanceMethod:(SEL)selector;
+ (BOOL)resolveClassMethod:(SEL)selector;**

具體代碼:

//People.m
void speak(id self, SEL _cmd){
    NSLog(@"Now I can speak.");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@"resolveInstanceMethod:  %@", NSStringFromSelector(sel));
    if (sel == @selector(speak)) {
        class_addMethod([self class], sel, (IMP)speak, "V@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

+ (BOOL)resolveClassMethod:(SEL)name  
{  
    NSLog(@" >> Class resolving %@", NSStringFromSelector(name));  
      
    return [super resolveClassMethod:name];  
}
  • 完整的消息轉發(fā)

完整的消息轉發(fā)又分為兩個階段脆粥,第一階段稱為備援接受者(replacement receiver)砌溺,第二階段才是啟動完整的消息轉發(fā)機制。

1.備援接收者

詢問別人是否可以幫助實現(xiàn)一下

 (id)forwardingTargetForSelector:(SEL)selector;

具體代碼:

- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"forwardingTargetForSelector:  %@", NSStringFromSelector(aSelector));
    Bird *bird = [[Bird alloc] init];
    if ([bird respondsToSelector:aSelector]) {
        return bird;
    }
    return [super forwardingTargetForSelector: aSelector];
}
// Bird.m
- (void)fly {
    NSLog(@"I am a bird, I can fly.");
}

方法參數(shù)代表未知的選擇子变隔,返回值為備援接受者规伐,若當前接受者能找到備援接受者,就直接返回匣缘,這個未知的選擇子將會交由備援接受者處理猖闪。如果找不到備援接受者,就返回nil肌厨,此時就會啟用“完整的消息轉發(fā)機制”培慌。

2.完整的消息轉發(fā)

- (void)forwardInvocation:(NSInvocation *)invocation;

核心實現(xiàn)代碼:

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"forwardInvocation: %@", NSStringFromSelector([anInvocation selector]));
    if ([anInvocation selector] == @selector(code)) {
       Monkey *monkey = [[Monkey alloc] init];
        [anInvocation invokeWithTarget:monkey];
    }   
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSLog(@"method signature for selector: %@", NSStringFromSelector(aSelector));
    if (aSelector == @selector(code)) {
        return [NSMethodSignature signatureWithObjCTypes:"V@:@"];
    }
    return [super methodSignatureForSelector:aSelector];
}

看完核心代碼還覺得慌,一臉霧水柑爸,具體怎么用呢检柬?代碼怎么寫?寫在哪里竖配?一切都準備在后面何址,不要慌,繼續(xù)往下看进胯,待著疑問看下去

實現(xiàn)此方法時用爪,如果發(fā)現(xiàn)調用操作不應該由本類處理,則需要沿著繼承體系胁镐,調用父類的同名方法偎血,這樣一來诸衔,繼承體系中的每個類都有機會處理這個調用請求,直至rootClass颇玷,也就是NSObject類笨农。如果最后調用了NSObject的類方法,那么該方法還會繼而調用”doesNotRecognizeSelector:“以拋出異常帖渠,此異常表明選擇子最終也未能得到處理谒亦。消息轉發(fā)到此結束。

最后消息未能處理的時候空郊,還會調用到

- (void)doesNotRecognizeSelector:(SEL)aSelector

我們也可以在這個方法中做些文章份招,避免掉crash,但是只建議在線上環(huán)境的時候做處理狞甚,實際開發(fā)過程中還要把異常拋出來锁摔。

完整消息轉發(fā)實例代碼:

代碼例子:

viewController中創(chuàng)建TestModel

    TestModel * model = [[TestModel alloc]init];
    [model testMethod];


TestModel.h

   -(void)testMethod;

TestModel.m

   -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    {
      if (aSelector == @selector(testMethod))
      {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
       }
      return nil;
  }

-(void)forwardInvocation:(NSInvocation *)anInvocation
 {
    if (anInvocation.selector == @selector(testMethod))
    {
        TestModelHelper1 *h1 = [[TestModelHelper1 alloc] init];
        TestModelHelper2 *h2 = [[TestModelHelper2 alloc] init];
        [anInvocation invokeWithTarget:h1];
        [anInvocation invokeWithTarget:h2];
    }
 }



@implementation TestModelHelper1
-(void)testMethod
{
    NSLog(@"i am TestModelHelper1");
}
@end



@implementation TestModelHelper2
-(void)testMethod
{
    NSLog(@"i am TestModelHelper2");
}
@end

運行可看到消息轉發(fā)日志

ps:方法緩存

通篇下來,發(fā)現(xiàn)調用一個方法并不像我們想的那么簡單哼审,更不像我們寫的那么簡單谐腰,一個方法的執(zhí)行其實底層需要很多步驟。正因如此涩盾,objc_msgSend()會將調用過且匹配到的方法緩存在”快速映射表(fast map)“中怔蚌,快速映射表就是方法的緩存表。每個類都有這樣一個緩存旁赊。所以桦踊,即便子類實例從父類的方法列表中取過了某個對象方法,那么子類的方法緩存表中也會緩存父類的這個方法终畅,下次調用這個方法籍胯,會優(yōu)先去當前類(對象所屬的類)的方法緩存表中查找這個方法,這樣的好處是顯而易見的离福,減少了漫長的方法查找過程杖狼,使得方法的調用更快。同樣妖爷,如果父類實例對象調用了同樣的方法蝶涩,也會在父類的方法緩存表中緩存這個方法。

同理絮识,如果用一個子類對象調用某個類方法绿聘,也會在子類的metaclass里緩存一份。而當用一個父類對象去調用那個類方法的時候次舌,也會在父類的metaclass里緩存一份熄攘。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市彼念,隨后出現(xiàn)的幾起案子挪圾,更是在濱河造成了極大的恐慌浅萧,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哲思,死亡現(xiàn)場離奇詭異洼畅,居然都是意外死亡,警方通過查閱死者的電腦和手機棚赔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門帝簇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人忆嗜,你說我怎么就攤上這事∑槠瘢” “怎么了捆毫?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長冲甘。 經常有香客問我绩卤,道長,這世上最難降的妖魔是什么江醇? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任濒憋,我火速辦了婚禮,結果婚禮上陶夜,老公的妹妹穿的比我還像新娘凛驮。我一直安慰自己,他們只是感情好条辟,可當我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布黔夭。 她就那樣靜靜地躺著,像睡著了一般羽嫡。 火紅的嫁衣襯著肌膚如雪本姥。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天杭棵,我揣著相機與錄音婚惫,去河邊找鬼。 笑死魂爪,一個胖子當著我的面吹牛先舷,可吹牛的內容都是我干的。 我是一名探鬼主播滓侍,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼密浑,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了粗井?” 一聲冷哼從身側響起尔破,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤街图,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后懒构,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體餐济,經...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年絮姆,在試婚紗的時候發(fā)現(xiàn)自己被綠了秩霍。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片篙悯。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡铃绒,死狀恐怖鸽照,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情颠悬,我是刑警寧澤矮燎,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布赔癌,位于F島的核電站,受9級特大地震影響灾票,放射性物質發(fā)生泄漏峡谊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一刊苍、第九天 我趴在偏房一處隱蔽的房頂上張望靖苇。 院中可真熱鬧,春花似錦班缰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽莹妒。三九已至,卻和暖如春旨怠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鉴腻。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工百揭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人器一。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓厨内,卻偏偏與公主長得像,于是被迫代替她去往敵國和親雏胃。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,941評論 2 355