Objective-C Runtime 運(yùn)行時(shí)之三:方法與消息

一、概述

前面我們討論了Runtime中對(duì)類(lèi)和對(duì)象的處理韧骗,及對(duì)成員變量與屬性的處理喉酌。這一章,我們就要開(kāi)始討論Runtime中最有意思的一部分:消息處理機(jī)制鹿霸。我們將詳細(xì)討論消息的發(fā)送及消息的轉(zhuǎn)發(fā)排吴。不過(guò)在討論消息之前,我們先來(lái)了解一下與方法相關(guān)的一些內(nèi)容懦鼠。

二钻哩、基礎(chǔ)數(shù)據(jù)類(lèi)型

  • SEL

SEL又叫選擇器,是表示一個(gè)方法的selector的指針肛冶,其定義如下:

typedef struct objc_selector *SEL;

objc_selector結(jié)構(gòu)體的詳細(xì)定義沒(méi)有在頭文件中找到街氢。方法的selector用于表示運(yùn)行時(shí)方 法的名字。Objective-C在編譯時(shí)睦袖,會(huì)依據(jù)每一個(gè)方法的名字珊肃、參數(shù)序列,生成一個(gè)唯一的整型標(biāo)識(shí)(Int類(lèi)型的地址)馅笙,這個(gè)標(biāo)識(shí)就是SEL伦乔。如下 代碼所示:

SEL sel1 = @selector(method1);
NSLog(@"sel : %p", sel1);

上面的輸出為:

2014-10-30 18:40:07.518 RuntimeTest[52734:466626] sel : 0x100002d72

兩個(gè)類(lèi)之間,不管它們是父類(lèi)與子類(lèi)的關(guān)系董习,還是之間沒(méi)有這種關(guān)系烈和,只要方法名相同,那么方法的SEL就是一樣的皿淋。每一個(gè)方法都對(duì)應(yīng)著一個(gè)SEL招刹。所以在 Objective-C同一個(gè)類(lèi)(及類(lèi)的繼承體系)中,不能存在2個(gè)同名的方法窝趣,即使參數(shù)類(lèi)型不同也不行疯暑。相同的方法只能對(duì)應(yīng)一個(gè)SEL。這也就導(dǎo)致 Objective-C在處理相同方法名且參數(shù)個(gè)數(shù)相同但類(lèi)型不同的方法方面的能力很差哑舒。如在某個(gè)類(lèi)中定義以下兩個(gè)方法:

- (void)setWidth:(int)width;
- (void)setWidth:(double)width;

當(dāng)然妇拯,不同的類(lèi)可以擁有相同的selector,這個(gè)沒(méi)有問(wèn)題散址。不同類(lèi)的實(shí)例對(duì)象執(zhí)行相同的selector時(shí)乖阵,會(huì)在各自的方法列表中去根據(jù)selector去尋找自己對(duì)應(yīng)的IMP。

工程中的所有的SEL組成一個(gè)Set集合预麸,Set的特點(diǎn)就是唯一瞪浸,因此SEL是唯一的。因此吏祸,如果我們想到這個(gè)方法集合中查找某個(gè)方法時(shí)对蒲,只需要去 找到這個(gè)方法對(duì)應(yīng)的SEL就行了钩蚊,SEL實(shí)際上就是根據(jù)方法名hash化了的一個(gè)字符串,而對(duì)于字符串的比較僅僅需要比較他們的地址就可以了蹈矮,可以說(shuō)速度 上無(wú)語(yǔ)倫比E槁摺!但是泛鸟,有一個(gè)問(wèn)題蝠咆,就是數(shù)量增多會(huì)增大hash沖突而導(dǎo)致的性能下降(或是沒(méi)有沖突,因?yàn)橐部赡苡玫氖莗erfect hash)北滥。但是不管使用什么樣的方法加速刚操,如果能夠?qū)⒖偭繙p少(多個(gè)方法可能對(duì)應(yīng)同一個(gè)SEL),那將是最犀利的方法再芋。那么菊霜,我們就不難理解,為什么 SEL僅僅是函數(shù)名了济赎。

本質(zhì)上鉴逞,SEL只是一個(gè)指向方法的指針(準(zhǔn)確的說(shuō),只是一個(gè)根據(jù)方法名hash化了的KEY值司训,能唯一代表一個(gè)方法)构捡,它的存在只是為了加快方法的查詢(xún)速度。這個(gè)查找過(guò)程我們將在下面討論豁遭。

我們可以在運(yùn)行時(shí)添加新的selector叭喜,也可以在運(yùn)行時(shí)獲取已存在的selector,我們可以通過(guò)下面三種方法來(lái)獲取SEL:

1. sel_registerName函數(shù)

2. Objective-C編譯器提供的@selector()

3. NSSelectorFromString()方法
  • IMP

IMP實(shí)際上是一個(gè)函數(shù)指針蓖谢,指向方法實(shí)現(xiàn)的首地址。其定義如下:

id (*IMP)(id, SEL, ...)

這個(gè)函數(shù)使用當(dāng)前CPU架構(gòu)實(shí)現(xiàn)的標(biāo)準(zhǔn)的C調(diào)用約定譬涡。第一個(gè)參數(shù)是指向self的指針(如果是實(shí)例方法闪幽,則是類(lèi)實(shí)例的內(nèi)存地址;如果是類(lèi)方法涡匀,則是指向元類(lèi)的指針)盯腌,第二個(gè)參數(shù)是方法選擇器(selector),接下來(lái)是方法的實(shí)際參數(shù)列表陨瘩。

前面介紹過(guò)的SEL就是為了查找方法的最終實(shí)現(xiàn)IMP的腕够。由于每個(gè)方法對(duì)應(yīng)唯一的SEL,因此我們可以通過(guò)SEL方便快速準(zhǔn)確地獲得它所對(duì)應(yīng)的 IMP舌劳,查找過(guò)程將在下面討論帚湘。取得IMP后,我們就獲得了執(zhí)行這個(gè)方法代碼的入口點(diǎn)甚淡,此時(shí)大诸,我們就可以像調(diào)用普通的C語(yǔ)言函數(shù)一樣來(lái)使用這個(gè)函數(shù)指針 了。

通過(guò)取得IMP,我們可以跳過(guò)Runtime的消息傳遞機(jī)制资柔,直接執(zhí)行IMP指向的函數(shù)實(shí)現(xiàn)焙贷,這樣省去了Runtime消息傳遞過(guò)程中所做的一系列查找操作,會(huì)比直接向?qū)ο蟀l(fā)送消息高效一些贿堰。

  • Method

介紹完SEL和IMP辙芍,我們就可以來(lái)講講Method了。Method用于表示類(lèi)定義中的方法羹与,則定義如下:

typedef struct objc_method *Method;
 
struct objc_method {
    SEL method_name                 OBJC2_UNAVAILABLE;  // 方法名
    char *method_types                  OBJC2_UNAVAILABLE;
    IMP method_imp                      OBJC2_UNAVAILABLE;  // 方法實(shí)現(xiàn)
}

我們可以看到該結(jié)構(gòu)體中包含一個(gè)SEL和IMP故硅,實(shí)際上相當(dāng)于在SEL和IMP之間作了一個(gè)映射。有了SEL注簿,我們便可以找到對(duì)應(yīng)的IMP契吉,從而調(diào)用方法的實(shí)現(xiàn)代碼。具體操作流程我們將在下面討論诡渴。

  • objc_method_description
struct objc_method_description { SEL name; char *types; };

三捐晶、方法相關(guān)操作函數(shù)

Runtime提供了一系列的方法來(lái)處理與方法相關(guān)的操作。包括方法本身及SEL妄辩。本節(jié)我們介紹一下這些函數(shù)惑灵。

  • 方法
方法操作相關(guān)函數(shù)包括下以:

// 調(diào)用指定方法的實(shí)現(xiàn)
id method_invoke ( id receiver, Method m, ... );
 
// 調(diào)用返回一個(gè)數(shù)據(jù)結(jié)構(gòu)的方法的實(shí)現(xiàn)
void method_invoke_stret ( id receiver, Method m, ... );
 
// 獲取方法名
SEL method_getName ( Method m );
 
// 返回方法的實(shí)現(xiàn)
IMP method_getImplementation ( Method m );
 
// 獲取描述方法參數(shù)和返回值類(lèi)型的字符串
const char * method_getTypeEncoding ( Method m );
 
// 獲取方法的返回值類(lèi)型的字符串
char * method_copyReturnType ( Method m );
 
// 獲取方法的指定位置參數(shù)的類(lèi)型字符串
char * method_copyArgumentType ( Method m, unsigned int index );
 
// 通過(guò)引用返回方法的返回值類(lèi)型字符串
void method_getReturnType ( Method m, char *dst, size_t dst_len );
 
// 返回方法的參數(shù)的個(gè)數(shù)
unsigned int method_getNumberOfArguments ( Method m );
 
// 通過(guò)引用返回方法指定位置參數(shù)的類(lèi)型字符串
void method_getArgumentType ( Method m, unsigned int index, char *dst, size_t dst_len );
 
// 返回指定方法的方法描述結(jié)構(gòu)體
struct objc_method_description * method_getDescription ( Method m );
 
// 設(shè)置方法的實(shí)現(xiàn)
IMP method_setImplementation ( Method m, IMP imp );
 
// 交換兩個(gè)方法的實(shí)現(xiàn)
void method_exchangeImplementations ( Method m1, Method m2 );
  1. method_invoke函數(shù),返回的是實(shí)際實(shí)現(xiàn)的返回值眼耀。參數(shù)receiver不能為空英支。這個(gè)方法的效率會(huì)比method_getImplementation和method_getName更快。

  2. method_getName函數(shù)哮伟,返回的是一個(gè)SEL干花。如果想獲取方法名的C字符串,可以使用sel_getName(method_getName(method))楞黄。

  3. method_getReturnType函數(shù)池凄,類(lèi)型字符串會(huì)被拷貝到dst中。

  4. method_setImplementation函數(shù)鬼廓,注意該函數(shù)返回值是方法之前的實(shí)現(xiàn)肿仑。

  • 方法選擇器

選擇器相關(guān)的操作函數(shù)包括:

// 返回給定選擇器指定的方法的名稱(chēng)
const char * sel_getName ( SEL sel );
 
// 在Objective-C Runtime系統(tǒng)中注冊(cè)一個(gè)方法,將方法名映射到一個(gè)選擇器碎税,并返回這個(gè)選擇器
SEL sel_registerName ( const char *str );
 
// 在Objective-C Runtime系統(tǒng)中注冊(cè)一個(gè)方法
SEL sel_getUid ( const char *str );
 
// 比較兩個(gè)選擇器
BOOL sel_isEqual ( SEL lhs, SEL rhs );
  1. sel_registerName函數(shù):在我們將一個(gè)方法添加到類(lèi)定義時(shí)尤慰,我們必須在Objective-C Runtime系統(tǒng)中注冊(cè)一個(gè)方法名以獲取方法的選擇器。

四雷蹂、方法調(diào)用流程

在Objective-C中伟端,消息直到運(yùn)行時(shí)才綁定到方法實(shí)現(xiàn)上。編譯器會(huì)將消息表達(dá)式[receiver message]轉(zhuǎn)化為一個(gè)消息函數(shù)的調(diào)用萎河,即objc_msgSend荔泳。這個(gè)函數(shù)將消息接收者和方法名作為其基礎(chǔ)參數(shù)蕉饼,如以下所示:

objc_msgSend(receiver, selector)

如果消息中還有其它參數(shù),則該方法的形式如下所示:

objc_msgSend(receiver, selector, arg1, arg2, ...)

這個(gè)函數(shù)完成了動(dòng)態(tài)綁定的所有事情:

  1. 首先它找到selector對(duì)應(yīng)的方法實(shí)現(xiàn)玛歌。因?yàn)橥粋€(gè)方法可能在不同的類(lèi)中有不同的實(shí)現(xiàn)昧港,所以我們需要依賴(lài)于接收者的類(lèi)來(lái)找到的確切的實(shí)現(xiàn)。

  2. 它調(diào)用方法實(shí)現(xiàn)支子,并將接收者對(duì)象及方法的所有參數(shù)傳給它创肥。

  3. 最后,它將實(shí)現(xiàn)返回的值作為它自己的返回值值朋。

消息的關(guān)鍵在于我們前面章節(jié)討論過(guò)的結(jié)構(gòu)體objc_class叹侄,這個(gè)結(jié)構(gòu)體有兩個(gè)字段是我們?cè)诜职l(fā)消息的關(guān)注的:

  1. 指向父類(lèi)的指針

  2. 一個(gè)類(lèi)的方法分發(fā)表,即methodLists昨登。

當(dāng)消息發(fā)送給一個(gè)對(duì)象時(shí)趾代,objc_msgSend通過(guò)對(duì)象的isa指針獲取到類(lèi)的結(jié)構(gòu)體,然后在方法分發(fā)表里面查找方法的selector丰辣。如果 沒(méi)有找到selector撒强,則通過(guò)objc_msgSend結(jié)構(gòu)體中的指向父類(lèi)的指針找到其父類(lèi),并在父類(lèi)的分發(fā)表里面查找方法的selector笙什。依 此飘哨,會(huì)一直沿著類(lèi)的繼承體系到達(dá)NSObject類(lèi)。一旦定位到selector琐凭,函數(shù)會(huì)就獲取到了實(shí)現(xiàn)的入口點(diǎn)芽隆,并傳入相應(yīng)的參數(shù)來(lái)執(zhí)行方法的具體實(shí) 現(xiàn)。如果最后沒(méi)有定位到selector统屈,則會(huì)走消息轉(zhuǎn)發(fā)流程胚吁,這個(gè)我們?cè)诤竺嬗懻摗?/p>

為了加速消息的處理,運(yùn)行時(shí)系統(tǒng)緩存使用過(guò)的selector及對(duì)應(yīng)的方法的地址愁憔。這點(diǎn)我們?cè)谇懊嬗懻撨^(guò)囤采,不再重復(fù)。

  • 隱藏參數(shù)

objc_msgSend有兩個(gè)隱藏參數(shù):

  1. 消息接收對(duì)象

  2. 方法的selector

這兩個(gè)參數(shù)為方法的實(shí)現(xiàn)提供了調(diào)用者的信息惩淳。之所以說(shuō)是隱藏的,是因?yàn)樗鼈冊(cè)诙x方法的源代碼中沒(méi)有聲明乓搬。它們是在編譯期被插入實(shí)現(xiàn)代碼的思犁。

雖然這些參數(shù)沒(méi)有顯示聲明,但在代碼中仍然可以引用它們进肯。我們可以使用self來(lái)引用接收者對(duì)象激蹲,使用_cmd來(lái)引用選擇器。如下代碼所示:

- strange
{
    id  target = getTheReceiver();
    SEL method = getTheMethod();
 
    if ( target == self || method == _cmd )
        return nil;
    return [target performSelector:method];
}

當(dāng)然江掩,這兩個(gè)參數(shù)我們用得比較多的是self学辱,_cmd在實(shí)際中用得比較少乘瓤。

  • 獲取方法地址

Runtime中方法的動(dòng)態(tài)綁定讓我們寫(xiě)代碼時(shí)更具靈活性,如我們可以把消息轉(zhuǎn)發(fā)給我們想要的對(duì)象策泣,或者隨意交換一個(gè)方法的實(shí)現(xiàn)等衙傀。不過(guò)靈活性的提 升也帶來(lái)了性能上的一些損耗。畢竟我們需要去查找方法的實(shí)現(xiàn)萨咕,而不像函數(shù)調(diào)用來(lái)得那么直接统抬。當(dāng)然,方法的緩存一定程度上解決了這一問(wèn)題危队。

我們上面提到過(guò)聪建,如果想要避開(kāi)這種動(dòng)態(tài)綁定方式,我們可以獲取方法實(shí)現(xiàn)的地址茫陆,然后像調(diào)用函數(shù)一樣來(lái)直接調(diào)用它金麸。特別是當(dāng)我們需要在一個(gè)循環(huán)內(nèi)頻繁地調(diào)用一個(gè)特定的方法時(shí),通過(guò)這種方式可以提高程序的性能簿盅。

NSObject類(lèi)提供了methodForSelector:方法挥下,讓我們可以獲取到方法的指針,然后通過(guò)這個(gè)指針來(lái)調(diào)用實(shí)現(xiàn)代碼挪鹏。我們需要將methodForSelector:返回的指針轉(zhuǎn)換為合適的函數(shù)類(lèi)型见秽,函數(shù)參數(shù)和返回值都需要匹配上。

我們通過(guò)以下代碼來(lái)看看methodForSelector:的使用:

void (*setter)(id, SEL, BOOL);
int i;
 
setter = (void (*)(id, SEL, BOOL))[target
    methodForSelector:@selector(setFilled:)];
for ( i = 0 ; i < 1000 ; i++ )
    setter(targetList[i], @selector(setFilled:), YES);

這里需要注意的就是函數(shù)指針的前兩個(gè)參數(shù)必須是id和SEL讨盒。

當(dāng)然這種方式只適合于在類(lèi)似于for循環(huán)這種情況下頻繁調(diào)用同一方法解取,以提高性能的情況。另外返顺,methodForSelector:是由Cocoa運(yùn)行時(shí)提供的禀苦;它不是Objective-C語(yǔ)言的特性。

五遂鹊、消息轉(zhuǎn)發(fā)

當(dāng)一個(gè)對(duì)象能接收一個(gè)消息時(shí)振乏,就會(huì)走正常的方法調(diào)用流程。但如果一個(gè)對(duì)象無(wú)法接收指定消息時(shí)秉扑,又會(huì)發(fā)生什么事呢慧邮?默認(rèn)情況下,如果是以 [object message]的方式調(diào)用方法舟陆,如果object無(wú)法響應(yīng)message消息時(shí)误澳,編譯器會(huì)報(bào)錯(cuò)。但如果是以perform…的形式來(lái)調(diào)用秦躯,則需要等到運(yùn) 行時(shí)才能確定object是否能接收message消息忆谓。如果不能,則程序崩潰踱承。

通常倡缠,當(dāng)我們不能確定一個(gè)對(duì)象是否能接收某個(gè)消息時(shí)哨免,會(huì)先調(diào)用respondsToSelector:來(lái)判斷一下。如下代碼所示:

if ([self respondsToSelector:@selector(method)]) {
    [self performSelector:@selector(method)];
}

不過(guò)昙沦,我們這邊想討論下不使用respondsToSelector:判斷的情況琢唾。這才是我們這一節(jié)的重點(diǎn)。

當(dāng)一個(gè)對(duì)象無(wú)法接收某一消息時(shí)桅滋,就會(huì)啟動(dòng)所謂”消息轉(zhuǎn)發(fā)(message forwarding)“機(jī)制慧耍,通過(guò)這一機(jī)制,我們可以告訴對(duì)象如何處理未知的消息丐谋。默認(rèn)情況下芍碧,對(duì)象接收到未知的消息,會(huì)導(dǎo)致程序崩潰号俐,通過(guò)控制臺(tái)泌豆,我們可以看到以下異常信息:

-[SUTRuntimeMethod method]: unrecognized selector sent to instance 0x100111940
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[SUTRuntimeMethod method]: unrecognized selector sent to instance 0x100111940'

這段異常信息實(shí)際上是由NSObject的”doesNotRecognizeSelector”方法拋出的。不過(guò)吏饿,我們可以采取一些措施踪危,讓我們的程序執(zhí)行特定的邏輯,而避免程序的崩潰猪落。

消息轉(zhuǎn)發(fā)機(jī)制基本上分為三個(gè)步驟:

  1. 動(dòng)態(tài)方法解析

  2. 備用接收者

  3. 完整轉(zhuǎn)發(fā)

下面我們?cè)敿?xì)討論一下這三個(gè)步驟贞远。

  • 動(dòng)態(tài)方法解析

對(duì)象在接收到未知的消息時(shí),首先會(huì)調(diào)用所屬類(lèi)的類(lèi)方法+resolveInstanceMethod:(實(shí)例方法)或 者+resolveClassMethod:(類(lèi)方法)笨忌。在這個(gè)方法中蓝仲,我們有機(jī)會(huì)為該未知消息新增一個(gè)“處理方法”。不過(guò)使用該方法的前提是我們已經(jīng)實(shí)現(xiàn)了該“處理方法”官疲,只需要在運(yùn)行時(shí)通過(guò)class_addMethod函數(shù)動(dòng)態(tài)添加到類(lèi)里面就可以了袱结。如下代碼所示:

void functionForMethod1(id self, SEL _cmd) {
   NSLog(@"%@, %p", self, _cmd);
}
 
+ (BOOL)resolveInstanceMethod:(SEL)sel {
 
    NSString *selectorString = NSStringFromSelector(sel);
 
    if ([selectorString isEqualToString:@"method1"]) {
        class_addMethod(self.class, @selector(method1), (IMP)functionForMethod1, "@:");
    }
 
    return [super resolveInstanceMethod:sel];
}

不過(guò)這種方案更多的是為了實(shí)現(xiàn)@dynamic屬性。

  • 備用接收者

如果在上一步無(wú)法處理消息途凫,則Runtime會(huì)繼續(xù)調(diào)以下方法:

- (id)forwardingTargetForSelector:(SEL)aSelector

如果一個(gè)對(duì)象實(shí)現(xiàn)了這個(gè)方法垢夹,并返回一個(gè)非nil的結(jié)果,則這個(gè)對(duì)象會(huì)作為消息的新接收者维费,且消息會(huì)被分發(fā)到這個(gè)對(duì)象果元。當(dāng)然這個(gè)對(duì)象不能是self自身,否則就是出現(xiàn)無(wú)限循環(huán)犀盟。當(dāng)然噪漾,如果我們沒(méi)有指定相應(yīng)的對(duì)象來(lái)處理aSelector,則應(yīng)該調(diào)用父類(lèi)的實(shí)現(xiàn)來(lái)返回結(jié)果且蓬。

使用這個(gè)方法通常是在對(duì)象內(nèi)部,可能還有一系列其它對(duì)象能處理該消息题翰,我們便可借這些對(duì)象來(lái)處理消息并返回恶阴,這樣在對(duì)象外部看來(lái)诈胜,還是由該對(duì)象親自處理了這一消息。如下代碼所示:

@interface SUTRuntimeMethodHelper : NSObject
 
- (void)method2;
 
@end
 
@implementation SUTRuntimeMethodHelper
 
- (void)method2 {
    NSLog(@"%@, %p", self, _cmd);
}
 
@end
 
#pragma mark -
 
@interface SUTRuntimeMethod () {
    SUTRuntimeMethodHelper *_helper;
}
 
@end
 
@implementation SUTRuntimeMethod
 
+ (instancetype)object {
    return [[self alloc] init];
}
 
- (instancetype)init {
    self = [super init];
    if (self != nil) {
        _helper = [[SUTRuntimeMethodHelper alloc] init];
    }
 
    return self;
}
 
- (void)test {
    [self performSelector:@selector(method2)];
}
 
- (id)forwardingTargetForSelector:(SEL)aSelector {
 
    NSLog(@"forwardingTargetForSelector");
 
    NSString *selectorString = NSStringFromSelector(aSelector);
 
    // 將消息轉(zhuǎn)發(fā)給_helper來(lái)處理
    if ([selectorString isEqualToString:@"method2"]) {
        return _helper;
    }
 
    return [super forwardingTargetForSelector:aSelector];
}
 
@end

這一步合適于我們只想將消息轉(zhuǎn)發(fā)到另一個(gè)能處理該消息的對(duì)象上冯事。但這一步無(wú)法對(duì)消息進(jìn)行處理焦匈,如操作消息的參數(shù)和返回值。

  • 完整消息轉(zhuǎn)發(fā)

如果在上一步還不能處理未知消息昵仅,則唯一能做的就是啟用完整的消息轉(zhuǎn)發(fā)機(jī)制了缓熟。此時(shí)會(huì)調(diào)用以下方法:

- (void)forwardInvocation:(NSInvocation *)anInvocation

運(yùn)行時(shí)系統(tǒng)會(huì)在這一步給消息接收者最后一次機(jī)會(huì)將消息轉(zhuǎn)發(fā)給其它對(duì)象。對(duì)象會(huì)創(chuàng)建一個(gè)表示消息的NSInvocation對(duì)象摔笤,把與尚未處理的消息 有關(guān)的全部細(xì)節(jié)都封裝在anInvocation中够滑,包括selector,目標(biāo)(target)和參數(shù)吕世。我們可以在forwardInvocation 方法中選擇將消息轉(zhuǎn)發(fā)給其它對(duì)象彰触。

forwardInvocation:方法的實(shí)現(xiàn)有兩個(gè)任務(wù):

  1. 定位可以響應(yīng)封裝在anInvocation中的消息的對(duì)象。這個(gè)對(duì)象不需要能處理所有未知消息命辖。

  2. 使用anInvocation作為參數(shù)况毅,將消息發(fā)送到選中的對(duì)象。anInvocation將會(huì)保留調(diào)用結(jié)果尔艇,運(yùn)行時(shí)系統(tǒng)會(huì)提取這一結(jié)果并將其發(fā)送到消息的原始發(fā)送者尔许。

不過(guò),在這個(gè)方法中我們可以實(shí)現(xiàn)一些更復(fù)雜的功能终娃,我們可以對(duì)消息的內(nèi)容進(jìn)行修改味廊,比如追回一個(gè)參數(shù)等,然后再去觸發(fā)消息尝抖。另外毡们,若發(fā)現(xiàn)某個(gè)消息不應(yīng)由本類(lèi)處理,則應(yīng)調(diào)用父類(lèi)的同名方法昧辽,以便繼承體系中的每個(gè)類(lèi)都有機(jī)會(huì)處理此調(diào)用請(qǐng)求衙熔。

還有一個(gè)很重要的問(wèn)題,我們必須重寫(xiě)以下方法:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

消息轉(zhuǎn)發(fā)機(jī)制使用從這個(gè)方法中獲取的信息來(lái)創(chuàng)建NSInvocation對(duì)象搅荞。因此我們必須重寫(xiě)這個(gè)方法红氯,為給定的selector提供一個(gè)合適的方法簽名。

完整的示例如下所示:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
 
    if (!signature) {
        if ([SUTRuntimeMethodHelper instancesRespondToSelector:aSelector]) {
            signature = [SUTRuntimeMethodHelper instanceMethodSignatureForSelector:aSelector];
        }
    }
 
    return signature;
}
 
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    if ([SUTRuntimeMethodHelper instancesRespondToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:_helper];
    }
}

NSObject的forwardInvocation:方法實(shí)現(xiàn)只是簡(jiǎn)單調(diào)用了doesNotRecognizeSelector:方法咕痛,它不會(huì)轉(zhuǎn)發(fā)任何消息痢甘。這樣,如果不在以上所述的三個(gè)步驟中處理未知消息茉贡,則會(huì)引發(fā)一個(gè)異常塞栅。

從某種意義上來(lái)講,forwardInvocation:就像一個(gè)未知消息的分發(fā)中心腔丧,將這些未知的消息轉(zhuǎn)發(fā)給其它對(duì)象放椰∽餮蹋或者也可以像一個(gè)運(yùn)輸站一樣將所有未知消息都發(fā)送給同一個(gè)接收對(duì)象。這取決于具體的實(shí)現(xiàn)砾医。

  • 消息轉(zhuǎn)發(fā)與多重繼承

回過(guò)頭來(lái)看第二和第三步拿撩,通過(guò)這兩個(gè)方法我們可以允許一個(gè)對(duì)象與其它對(duì)象建立關(guān)系,以處理某些未知消息如蚜,而表面上看仍然是該對(duì)象在處理消息压恒。通過(guò)這 種關(guān)系,我們可以模擬“多重繼承”的某些特性错邦,讓對(duì)象可以“繼承”其它對(duì)象的特性來(lái)處理一些事情探赫。不過(guò),這兩者間有一個(gè)重要的區(qū)別:多重繼承將不同的功能 集成到一個(gè)對(duì)象中兴猩,它會(huì)讓對(duì)象變得過(guò)大期吓,涉及的東西過(guò)多;而消息轉(zhuǎn)發(fā)將功能分解到獨(dú)立的小的對(duì)象中倾芝,并通過(guò)某種方式將這些對(duì)象連接起來(lái)讨勤,并做相應(yīng)的消息轉(zhuǎn)發(fā)。

不過(guò)消息轉(zhuǎn)發(fā)雖然類(lèi)似于繼承晨另,但NSObject的一些方法還是能區(qū)分兩者潭千。如respondsToSelector:和isKindOfClass:只能用于繼承體系,而不能用于轉(zhuǎn)發(fā)鏈借尿。便如果我們想讓這種消息轉(zhuǎn)發(fā)看起來(lái)像是繼承刨晴,則可以重寫(xiě)這些方法,如以下代碼所示:

- (BOOL)respondsToSelector:(SEL)aSelector   {
       if ( [super respondsToSelector:aSelector] )
                return YES;     
       else {
                 /* Here, test whether the aSelector message can
                  *            
                  * be forwarded to another object and whether that  
                  *            
                  * object can respond to it. Return YES if it can.  
                  */      
       }
       return NO;  
}

六路翻、小結(jié)

在此狈癞,我們已經(jīng)了解了Runtime中消息發(fā)送和轉(zhuǎn)發(fā)的基本機(jī)制。這也是Runtime的強(qiáng)大之處茂契,通過(guò)它蝶桶,我們可以為程序增加很多動(dòng)態(tài)的行為,雖 然我們?cè)趯?shí)際開(kāi)發(fā)中很少直接使用這些機(jī)制(如直接調(diào)用objc_msgSend)掉冶,但了解它們有助于我們更多地去了解底層的實(shí)現(xiàn)真竖。其實(shí)在實(shí)際的編碼過(guò)程中,我們也可以靈活地使用這些機(jī)制厌小,去實(shí)現(xiàn)一些特殊的功能恢共,如hook操作等。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末璧亚,一起剝皮案震驚了整個(gè)濱河市讨韭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖拐袜,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吉嚣,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡蹬铺,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén)秉撇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)甜攀,“玉大人,你說(shuō)我怎么就攤上這事琐馆」娣В” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵瘦麸,是天一觀的道長(zhǎng)谁撼。 經(jīng)常有香客問(wèn)我,道長(zhǎng)滋饲,這世上最難降的妖魔是什么厉碟? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮屠缭,結(jié)果婚禮上箍鼓,老公的妹妹穿的比我還像新娘。我一直安慰自己呵曹,他們只是感情好款咖,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著奄喂,像睡著了一般铐殃。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上跨新,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天富腊,我揣著相機(jī)與錄音,去河邊找鬼玻蝌。 笑死蟹肘,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的俯树。 我是一名探鬼主播帘腹,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼许饿!你這毒婦竟也來(lái)了阳欲?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎球化,沒(méi)想到半個(gè)月后秽晚,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡筒愚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年赴蝇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片巢掺。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡句伶,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出陆淀,到底是詐尸還是另有隱情考余,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布轧苫,位于F島的核電站楚堤,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏含懊。R本人自食惡果不足惜身冬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望绢要。 院中可真熱鬧吏恭,春花似錦、人聲如沸重罪。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)剿配。三九已至搅幅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間呼胚,已是汗流浹背茄唐。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蝇更,地道東北人沪编。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像年扩,于是被迫代替她去往敵國(guó)和親蚁廓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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