OC消息傳遞機(jī)制三道防線:消息轉(zhuǎn)發(fā)機(jī)制詳解

消息傳遞機(jī)制

在OC中,方法的調(diào)用不再理解為對(duì)象調(diào)用其方法,而是要理解成對(duì)象接收消息,消息的發(fā)送采用‘動(dòng)態(tài)綁定’機(jī)制,具體會(huì)調(diào)用哪個(gè)方法直到運(yùn)行時(shí)才能確定趁冈,確定后才會(huì)去執(zhí)行綁定的代碼。方法的調(diào)用實(shí)際就是告訴對(duì)象要干什么拜马,給對(duì)象(的指針)傳送一個(gè)消息渗勘,對(duì)象為接收者(receiver),調(diào)用的方法及其參數(shù)即消息(message)俩莽,給一個(gè)對(duì)象傳消息表達(dá)為:[receiver message]; 接受者的類型可以通過(guò)動(dòng)態(tài)類型識(shí)別于運(yùn)行時(shí)確定旺坠。
在消息傳遞機(jī)制中,當(dāng)開(kāi)發(fā)者編寫[receiver message];語(yǔ)句發(fā)送消息后豹绪,編譯器都會(huì)將其轉(zhuǎn)換成對(duì)應(yīng)的一條objc_msgSend C語(yǔ)言消息發(fā)送原語(yǔ)价淌,具體格式為: void objc_msgSend (id self, SEL cmd, ...)

這個(gè)原語(yǔ)函數(shù)參數(shù)可變,第一個(gè)參數(shù)填入消息的接受者瞒津,第二個(gè)參數(shù)是消息‘選擇子’蝉衣,后面跟著可選的消息的參數(shù)。有了這些參數(shù)巷蚪,objc_msgSend就可以通過(guò)接受者的的isa指針病毡,到其類對(duì)象中的方法列表中以選擇子的名稱為‘鍵’尋找對(duì)應(yīng)的方法,找到則轉(zhuǎn)到其實(shí)現(xiàn)代碼執(zhí)行屁柏,找不到則繼續(xù)根據(jù)繼承關(guān)系從父類中尋找啦膜,如果到了根類還是無(wú)法找到對(duì)應(yīng)的方法有送,說(shuō)明該接受者對(duì)象無(wú)法響應(yīng)該消息,則會(huì)觸發(fā)‘消息轉(zhuǎn)發(fā)機(jī)制’僧家,給開(kāi)發(fā)者最后一次挽救程序崩潰的機(jī)會(huì)雀摘。

消息轉(zhuǎn)發(fā)機(jī)制:

如果消息傳遞過(guò)程中,接受者無(wú)法響應(yīng)收到的消息八拱,則會(huì)觸發(fā)進(jìn)入‘消息轉(zhuǎn)發(fā)’機(jī)制阵赠。

消息轉(zhuǎn)發(fā)依次提供了三道防線,任何一個(gè)起作用都可以挽救此次消息轉(zhuǎn)發(fā)肌稻。按照先后順序三道防線依次為:

動(dòng)態(tài)補(bǔ)加方法的實(shí)現(xiàn)

+ (BOOL)resolveInstanceMethod:(SEL)sel
+ (BOOL)resolveClassMethod:(SEL)sel

直接返回消息轉(zhuǎn)發(fā)到的對(duì)象(將消息發(fā)送給另一個(gè)對(duì)象去處理)

- (id)forwardingTargetForSelector:(SEL)aSelector

手動(dòng)生成方法簽名并轉(zhuǎn)發(fā)給另一個(gè)對(duì)象

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;

示例

這里以一個(gè)簡(jiǎn)單的例子展示消息轉(zhuǎn)發(fā)的完整個(gè)過(guò)程清蚀。定義一個(gè)Test類,類頭文件聲明一個(gè)名為instanceMethod的實(shí)例方法但不提供方法實(shí)現(xiàn)(消息轉(zhuǎn)發(fā)主要就針對(duì)實(shí)例方法爹谭,類方法由于無(wú)法在運(yùn)行時(shí)動(dòng)態(tài)添加實(shí)現(xiàn)等事實(shí)并不能轉(zhuǎn)發(fā)給其他類):

/* Test.h */
@interface Test : NSObject
/* 只聲明一個(gè)實(shí)例方法而不在.m文件中實(shí)現(xiàn) */
- (void)instanceMethod;
@end

然后在main函數(shù)中實(shí)例化Test對(duì)象并調(diào)用該實(shí)例方法枷邪,由于方法沒(méi)有實(shí)現(xiàn),因此在運(yùn)行時(shí)一定會(huì)觸發(fā)消息轉(zhuǎn)發(fā)機(jī)制:

/* main.m */
#import "Test.h"
#import <objc/runtime.h>

int main(int argc, const char * argv[]) {
    Test *test = [[Test alloc] init];
    [test instanceMethod];
    return 0;
}

先進(jìn)入消息轉(zhuǎn)發(fā)的第一道防線诺凡,我們?cè)赥est類的.m文件中提供運(yùn)行時(shí)的轉(zhuǎn)發(fā)接應(yīng)东揣,實(shí)現(xiàn)resolveInstanceMethod方法為指定的instanceMethod消息補(bǔ)加對(duì)應(yīng)方法的實(shí)現(xiàn)完成補(bǔ)救:

/* Test.m */
#import <objc/runtime.h>
/*
 * 被動(dòng)態(tài)添加的實(shí)例方法實(shí)現(xiàn)
 */
void instanceMethod(id self, SEL _cmd) {
    NSLog(@"收到消息后會(huì)執(zhí)行此處的函數(shù)實(shí)現(xiàn)...");
}

/*
 * 動(dòng)態(tài)補(bǔ)加方法實(shí)現(xiàn)
 */
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(instanceMethod)) {
        class_addMethod(self, sel, (IMP)instanceMethod, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

如果沒(méi)有實(shí)現(xiàn)resolveInstanceMethod方法就行補(bǔ)救或者直接返回了NO,則進(jìn)入第二道防線绑洛,這里我們要實(shí)現(xiàn)forwardingTargetForSelector函數(shù)返回另一個(gè)實(shí)例對(duì)象救斑,讓該對(duì)象代替原對(duì)象去處理這個(gè)消息童本。 假設(shè)我們讓一個(gè)叫做Test2的類對(duì)象去處理這個(gè)消息真屯,Test2類中要有同名的方法和方法的實(shí)現(xiàn),這樣就會(huì)執(zhí)行Test2中的同名方法完成消息轉(zhuǎn)發(fā):

/* Test2.h */
@interface Test2 : NSObject
- (void)instanceMethod;
@end

/* Test2.m */
@implementation Test2
- (void)instanceMethod {
    NSLog(@"消息轉(zhuǎn)發(fā)到這...");
}
@end

/* Test.m */
- (id)forwardingTargetForSelector:(SEL)aSelector {
    /* 返回轉(zhuǎn)發(fā)的對(duì)象實(shí)例 */
    if (aSelector == @selector(instanceMethod)) {
        return [[Test2 alloc] init];
    }
    return nil;
}

如果沒(méi)有實(shí)現(xiàn)上面的兩個(gè)補(bǔ)救方法或者forwardingTargetForSelector方法直接返回了nil,則進(jìn)入最后一道防線穷娱,此時(shí)我們要手動(dòng)生成方法簽名并實(shí)現(xiàn)forwardInvocation方法將消息轉(zhuǎn)發(fā)給另一個(gè)對(duì)象绑蔫,同第二道防線類似:

/* Test.m */
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    /* 為指定的方法手動(dòng)生成簽名 */
    NSString *selName = NSStringFromSelector(aSelector);
    if ([selName isEqualToString:@"instanceMethod"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    /* 如果另一個(gè)對(duì)象可以相應(yīng)該消息,則將消息轉(zhuǎn)發(fā)給他 */
    SEL sel = [anInvocation selector];
    Test2 *test2 = [[Test2 alloc] init];
    if ([test2 respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:test2];
    }
}

完整代碼

#import <Foundation/Foundation.h>
#import "Test.h"

int main(int argc, const char * argv[]) {
    Test *test = [[Test alloc] init];
    [test instanceMethod];
    return 0;
}
#import <Foundation/Foundation.h> 
@interface Test : NSObject

- (void)instanceMethod;

@end


#import "Test.h" #import "Test2.h" #import <objc/runtime.h> 
@implementation Test
/* * 被動(dòng)態(tài)添加的實(shí)例方法實(shí)現(xiàn) */
void instanceMethod(id self, SEL _cmd) {
    NSLog(@"收到消息會(huì)執(zhí)行此處的函數(shù)實(shí)現(xiàn)...");
}

/* * 1.第一道防線:動(dòng)態(tài)補(bǔ)加方法實(shí)現(xiàn) */
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    // return NO;     if (sel == @selector(instanceMethod)) {
        class_addMethod(self, sel, (IMP)instanceMethod, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

/* * 2.第二道防線 */
- (id)forwardingTargetForSelector:(SEL)aSelector {
    // return nil;     /* 返回轉(zhuǎn)發(fā)的對(duì)象實(shí)例 */
    if (aSelector == @selector(instanceMethod)) {
        return [[Test2 alloc] init];
    }
    return nil;
}

/* * 3.第三道防線 */
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    /* 為指定的方法手動(dòng)生成簽名 */
    NSString *selName = NSStringFromSelector(aSelector);
    if ([selName isEqualToString:@"instanceMethod"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    /* 如果另一個(gè)對(duì)象可以相應(yīng)該消息泵额,則將消息轉(zhuǎn)發(fā)給他 */
    SEL sel = [anInvocation selector];
    Test2 *test2 = [[Test2 alloc] init];
    if ([test2 respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:test2];
    }
}

@end
#import <Foundation/Foundation.h> 
@interface Test2 : NSObject

- (void)instanceMethod;

@end


#import "Test2.h" 
@implementation Test2

- (void)instanceMethod {
    NSLog(@"消息轉(zhuǎn)發(fā)到這...");
}

@end
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末配深,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子嫁盲,更是在濱河造成了極大的恐慌篓叶,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件羞秤,死亡現(xiàn)場(chǎng)離奇詭異缸托,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)瘾蛋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門俐镐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人哺哼,你說(shuō)我怎么就攤上這事佩抹〉鸱纾” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵棍苹,是天一觀的道長(zhǎng)无宿。 經(jīng)常有香客問(wèn)我,道長(zhǎng)枢里,這世上最難降的妖魔是什么懈贺? 我笑而不...
    開(kāi)封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮坡垫,結(jié)果婚禮上梭灿,老公的妹妹穿的比我還像新娘。我一直安慰自己冰悠,他們只是感情好堡妒,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著溉卓,像睡著了一般皮迟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上桑寨,一...
    開(kāi)封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天伏尼,我揣著相機(jī)與錄音,去河邊找鬼尉尾。 笑死爆阶,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的沙咏。 我是一名探鬼主播辨图,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼肢藐!你這毒婦竟也來(lái)了故河?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤吆豹,失蹤者是張志新(化名)和其女友劉穎鱼的,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體痘煤,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡凑阶,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了速勇。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晌砾。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖烦磁,靈堂內(nèi)的尸體忽然破棺而出养匈,到底是詐尸還是另有隱情哼勇,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布呕乎,位于F島的核電站积担,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏猬仁。R本人自食惡果不足惜帝璧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望湿刽。 院中可真熱鬧的烁,春花似錦、人聲如沸诈闺。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)雅镊。三九已至襟雷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間仁烹,已是汗流浹背耸弄。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留卓缰,地道東北人计呈。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像僚饭,于是被迫代替她去往敵國(guó)和親震叮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子胧砰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

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