runtime之objc_msgSend和消息轉(zhuǎn)發(fā)

前言

OC是一門動(dòng)態(tài)語(yǔ)言,就預(yù)示這光靠編譯過(guò)程來(lái)完全讀懂工作的方式是不夠的,很多執(zhí)行語(yǔ)句都需要在運(yùn)行時(shí)才能知道其意思,也就是runtime。

理解OC中的消息傳遞

在Object-C中,函數(shù)的調(diào)用過(guò)程經(jīng)常性的使用到方法,用來(lái)傳遞消息。而消息包括名稱或選擇子(selector),以及參數(shù)和返回值。
oc是一門動(dòng)態(tài)語(yǔ)言,也就說(shuō)明其消息可以是動(dòng)態(tài)綁定的過(guò)程,在編譯時(shí),無(wú)法決定運(yùn)行時(shí)該調(diào)用的函數(shù)。
例如該語(yǔ)句我們沒(méi)有辦法去知道返回值的類型:

id value = [Person sendMessage:@"黃"];

整個(gè)合起來(lái)就是一個(gè)消息,在其中,Person為接收者(receiver),sendMessage為選擇子,后續(xù)為參數(shù)。當(dāng)編譯器收到該條消息是,通過(guò)C語(yǔ)言函數(shù),也是消息傳遞機(jī)制中的核心函數(shù)objc_msgSend進(jìn)行消息傳遞。

void objc_msgSend(id self, SEL cmd, ...)

該函數(shù)能接受多個(gè)參數(shù),其中第一個(gè)參數(shù)代表接收者,第二個(gè)參數(shù)是選擇子,后跟可以跟多個(gè)發(fā)送消息參數(shù)。

objc_msgSend 消息傳遞機(jī)制

objc_msgSend會(huì)先判斷接收者對(duì)象是否存在,如果存在情況根據(jù)接收者所屬的類中搜尋”方法列表“,如果找不到,則會(huì)向上找繼承類的”方法列表“,找打找到合適就跳轉(zhuǎn),如果找不到則執(zhí)行消息轉(zhuǎn)發(fā)(后期會(huì)更新消息轉(zhuǎn)發(fā)機(jī)制)。

該過(guò)程雖然繁瑣,但是objc_msgSend會(huì)為將每將匹配的結(jié)果緩存到”快速映射表“中,每個(gè)類都存在這樣的緩存,后續(xù)若是需要再發(fā)送同樣的消息,可以直接查找映射表,節(jié)省執(zhí)行時(shí)間。

后續(xù)消息處理工作,會(huì)交給一些函數(shù)來(lái)處理:
objc_msgSend_stret如果待發(fā)送的消息要返回結(jié)構(gòu)體,可交由此函數(shù)處理。
objc_msgSend_fpret如果待發(fā)送的消息要返回浮點(diǎn)數(shù),可交由此函數(shù)處理。
objc_msgSendSuper如果給父類發(fā)送消息,可交由此函數(shù)處理。

前面提到消息一旦找到就會(huì)跳轉(zhuǎn)過(guò)去,之所以可以跳轉(zhuǎn),是因?yàn)镺C對(duì)象的每個(gè)方法視為簡(jiǎn)單的C函數(shù)。

void Class_selector(id self, SEL cmd, ...)

每個(gè)類都有一張表,其中的指針都會(huì)指向這種函數(shù),oc則以方法名作為key,來(lái)查表并執(zhí)行跳轉(zhuǎn)。

為了優(yōu)化跳轉(zhuǎn)方法變得簡(jiǎn)單一些,采用”尾調(diào)用優(yōu)化“方式:如果某函數(shù)的最后一個(gè)操作是跳轉(zhuǎn)到另一個(gè)函數(shù),編譯器會(huì)生成跳轉(zhuǎn)所需的指令碼,而不是向棧中新增新的幀,這樣減少了,每次調(diào)用objc_msgSend需要新增棧幀的問(wèn)題,還有減少”棧溢出“的發(fā)生。但是僅在調(diào)用其他函數(shù)時(shí),而不會(huì)把返回值另做他用的情況下。

根據(jù)上面的描述總結(jié)消息發(fā)送的步驟:

  1. 檢查接收者對(duì)象是否為nil,是則直接結(jié)束轉(zhuǎn)發(fā),否則進(jìn)入下一步;
  2. 查看類緩存的”快速映射表“中是否緩存該方法,該映射方法,是直接發(fā)送消息,否則進(jìn)入下一步;
  3. 查看類的”方法列表“中是否存在,是緩存到映射表,并發(fā)送消息,否則進(jìn)入下一步;
  4. 查看父類的”快速映射表“是否緩存改方法,有就發(fā)送,否則進(jìn)入下一步;
  5. 查看父類的"方法列表"中是否存在,是緩存到映射表,并發(fā)送消息,否則回到第4步繼續(xù)查找更上層的父類;
  6. 查找到?jīng)]有父類之后,就說(shuō)明并沒(méi)有該方法,但是還不一定結(jié)束,后面會(huì)去進(jìn)入消息轉(zhuǎn)發(fā)機(jī)制的步驟。
動(dòng)態(tài)方法解析

上述說(shuō)到,當(dāng)發(fā)送消息時(shí),會(huì)出現(xiàn)無(wú)法解讀消息的情況。即當(dāng)消息發(fā)送到最后,依然找不到接收消息的方式時(shí),oc對(duì)該消息可以進(jìn)行更多處理,也就是動(dòng)態(tài)方法解析消息轉(zhuǎn)發(fā)。

oc是動(dòng)態(tài)語(yǔ)言,在消息找不到目標(biāo)時(shí),編譯器無(wú)法解析錯(cuò)誤情況并不會(huì)報(bào)錯(cuò),因?yàn)樵谶\(yùn)行時(shí)可以動(dòng)態(tài)的給類添加方法,所以當(dāng)無(wú)法解析消息是,就會(huì)啟動(dòng)消息轉(zhuǎn)發(fā)機(jī)制,可由程序員手動(dòng)動(dòng)態(tài)處理后續(xù)的消息問(wèn)題。

如果程序員沒(méi)有去處理這些問(wèn)題,則會(huì)直接crach掉:

- (void)viewDidLoad {
    [super viewDidLoad];
    // 去調(diào)用一個(gè)沒(méi)有實(shí)現(xiàn)的方法后
    [self test];
}
// 刪掉實(shí)現(xiàn)方法
//- (void)test { }

// 閃退并打印以下
*** -[ViewController test]: unrecognized selector sent to instance 0x100e01440
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', 
reason: '-[ViewController test]: unrecognized selector sent to instance 0x100e01440'

我們只調(diào)用test方法,但是沒(méi)有調(diào)用實(shí)現(xiàn)方法,因此,如果沒(méi)有做異常處理的話,會(huì)直接閃退。

消息轉(zhuǎn)發(fā)兩個(gè)階段:

第一階段是動(dòng)態(tài)方法解析,先征詢接收者所屬的類,看是否能動(dòng)態(tài)添加方法,以處理當(dāng)>前”未知選擇子(調(diào)用的方法)“。
第二階段涉及完整的消息轉(zhuǎn)發(fā)機(jī)制,若是第一階段可以順利完成,就不會(huì)啟動(dòng)消息轉(zhuǎn)發(fā)。

顯然如果能越早對(duì)消息進(jìn)行處理,則減少運(yùn)行時(shí)所耗費(fèi)的時(shí)長(zhǎng)。

動(dòng)態(tài)方法解析有兩個(gè)方法,包括實(shí)例方法解析和類方法解析

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

在該例子中,如所屬類添加了resolveInstanceMethod方法去處理,當(dāng)調(diào)用的方法沒(méi)有找到實(shí)現(xiàn)目標(biāo)時(shí),就會(huì)調(diào)用該實(shí)例方法去處理。

void dynamicMethodIMP(id self, SEL _cmd) { }
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // 去調(diào)用一個(gè)沒(méi)有實(shí)現(xiàn)的方法后
    [self test];
}
// 刪掉實(shí)現(xiàn)方法
//- (void)test { }

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(test)) {
        class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
@end

如上圖所示,不會(huì)異常閃退,因?yàn)楫?dāng)調(diào)用test方法時(shí),找不到會(huì)直接調(diào)用resolveInstanceMethod,而在resolveInstanceMethod中對(duì)test,通過(guò)class_addMethod動(dòng)態(tài)添加。

備援接收者

如果resolveInstanceMethod或者resolveClassMethod返回YES就直接結(jié)束,如果返回NO,說(shuō)明動(dòng)態(tài)解析沒(méi)有創(chuàng)建動(dòng)態(tài)方法去處理,當(dāng)前接收者還有第二次機(jī)會(huì)能處理未知選擇子,在運(yùn)行時(shí),到這一步,就會(huì)問(wèn)這條消息能不能轉(zhuǎn)發(fā)給其他接收者處理。

- (id)forwardingTargetForSelector:(SEL)aSelector {
    Person *person = [[Person alloc]init];

    if (aSelector == @selector(test)) {
        return person;
    } else {
       return [super forwardingTargetForSelector:aSelector];
    }
}

當(dāng)調(diào)用到這一步,如果返回對(duì)象,則是在告訴編譯器,把這條消息傳遞給person對(duì)象來(lái)處理。到這里就不會(huì)出現(xiàn)異常,如果直接返回nil或者沒(méi)有該對(duì)象,說(shuō)明依然沒(méi)辦法處理結(jié)束并拋出異常。

注意:我們無(wú)法經(jīng)由這一步操作消息轉(zhuǎn)發(fā),如果需要修改消息的內(nèi)容,就要啟用完整的消息轉(zhuǎn)發(fā)機(jī)制。

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

到這一步,說(shuō)明只能啟用消息轉(zhuǎn)發(fā),通過(guò)將有關(guān)消息的全部?jī)?nèi)容,創(chuàng)建NSInvocation對(duì)象,并封裝內(nèi)容包括選擇子、目標(biāo)及參數(shù)。再觸發(fā)NSInvocation對(duì)象時(shí),通過(guò)’消息派發(fā)系統(tǒng)‘將消息指派給目標(biāo)對(duì)象。

- (void)forwardInvocation:(NSInvocation *)anInvocation

但是使用消息轉(zhuǎn)發(fā)時(shí)必須為另一個(gè)類實(shí)現(xiàn)的消息創(chuàng)建一個(gè)有效的方法簽名:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

實(shí)現(xiàn)的方式如下所致,其與第二步消息轉(zhuǎn)發(fā)等效,但是卻可以在消息觸發(fā)前,改變消息的內(nèi)容。比如:追加一個(gè)參數(shù)或者改變選擇子等。

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


- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL selector = [anInvocation selector];
    // 新建需要轉(zhuǎn)發(fā)消息的對(duì)象
    Cat *cat = [[Cat alloc] init];
    if ([cat respondsToSelector:selector]) {
        // 喚醒這個(gè)方法
        [anInvocation invokeWithTarget:cat];
    }
}

以上就是消息轉(zhuǎn)發(fā)的幾個(gè)步驟,每一步均有機(jī)會(huì)處理消息。步驟越往后,處理消息的代價(jià)就越大,如果可以在第一步就把消息處理完,運(yùn)行時(shí)也可以將消息緩存起來(lái)。如果放到最后再做處理的話,不僅復(fù)雜還需要?jiǎng)?chuàng)建和處理完整的NSInvocation對(duì)象。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容