iOS 消息發(fā)送、轉(zhuǎn)發(fā)機(jī)制

消息直到運(yùn)行時(shí)才會與方法實(shí)現(xiàn)進(jìn)行綁定(在OC中方法調(diào)用是一個(gè)消息發(fā)送的過程)

OC中調(diào)用方法:

[xiaoming eat: apple];

在方法調(diào)用的時(shí)候,runtime會將上面的方法調(diào)用轉(zhuǎn)化成一個(gè)C語言的函數(shù)調(diào)用,表示朝著?xiaoming?發(fā)了一個(gè)eat:消息,并傳入了apple這個(gè)參數(shù):

objc_msgSend(xiaoming, @selector(eat:), apple);

消息發(fā)送的主要步驟如下:

1.首先檢查這個(gè)selector是不是要忽略。

2.檢測這個(gè)selector的target是不是nil,OC允許我們對一個(gè)nil對象執(zhí)行任何方法不會Crash,因?yàn)檫\(yùn)行時(shí)會被忽略掉。

3.如果上面兩步都通過了,就開始查找這個(gè)類的實(shí)現(xiàn)IMP,先從cache里查找,如果找到了就運(yùn)行對應(yīng)的函數(shù)去執(zhí)行相應(yīng)的代碼。

4.如果cache中沒有找到就找類的方法列表中是否有對應(yīng)的方法。

5.如果類的方法列表中找不到就到父類的方法列表中查找,一直找到NSObject類為止。

6.如果還是沒找到就要開始進(jìn)入動態(tài)方法解析(動態(tài)添加、重定向....)

在消息的傳遞中,編譯器會根據(jù)情況在objc_msgSend? 、objc_msgSend_stret 、objc_msgSendSuper、objc_msgSendSuper_stret? 這四個(gè)方法中選擇一個(gè)調(diào)用。如果消息是傳遞給父類,那么會調(diào)用名字帶有super的函數(shù),如果消息返回值是數(shù)據(jù)結(jié)構(gòu)而不是簡單值時(shí),會調(diào)用名字帶有stret的函數(shù)。

動態(tài)方法解析

當(dāng)runtime系統(tǒng)在Cache和類的方法列表(包括父類)中找不到要執(zhí)行的方法時(shí)runtime會調(diào)用resolveInstanceMethod: 或者resolveClassMethod: 來給我們一次動態(tài)添加方法實(shí)現(xiàn)的機(jī)會。我們需要用class_addMethod函數(shù)完成向特定類添加特定方法實(shí)現(xiàn)的操作:

void dynamicMethodIMP(id self, SEL _cmd) {

// implementation ....

}

@implementation MyClass

+ (BOOL)resolveInstanceMethod:(SEL)aSEL

{

if (aSEL == @selector(resolveThisMethodDynamically)) {

class_addMethod([self class], aSEL, (IMP)

dynamicMethodIMP, "v@:");

return YES;

}

return [super resolveInstanceMethod:aSEL];

}

@end

上面的例子為 resolveThisMethodDynamically 這個(gè)方法添加了實(shí)現(xiàn)內(nèi)容,就是dynamicMethodIMP方法中的代碼。其中"v@:"表示返回值跟參數(shù)。注意:動態(tài)方法解析是在上面說的第6步后,消息轉(zhuǎn)發(fā)機(jī)制侵入之前執(zhí)行,動態(tài)方法解析器將會首先給予提供該方法選擇器對應(yīng)的IMP的機(jī)會。如果你想讓該方法選擇器被傳送到轉(zhuǎn)發(fā)機(jī)制,就讓resolveInstanceMethod:方法返回NO。

1.重定向

消息轉(zhuǎn)發(fā)機(jī)制執(zhí)行前,runtime系統(tǒng)允許我們替換消息的接收者為其他對象。通過- (id)forwardingTargetForSelector:(SEL)aSelector方法。

- (id)forwardingTargetForSelector:(SEL)aSelector

{

if(aSelector == @selector(mysteriousMethod:)){

return alternateObject;

}

return [super forwardingTargetForSelector:aSelector];

}

如果此方法返回的是nil 或者self,則會進(jìn)入消息轉(zhuǎn)發(fā)機(jī)制(forwardInvocation:),否則將會向返回的對象重新發(fā)送消息。

2.轉(zhuǎn)發(fā)

當(dāng)動態(tài)方法解析不做處理返回NO時(shí),則會觸發(fā)消息轉(zhuǎn)發(fā)機(jī)制。這時(shí)forwardInvocation:方法會被執(zhí)行,可以重寫這個(gè)方法來自定義我們的轉(zhuǎn)發(fā)邏輯:

- (void)forwardInvocation:(NSInvocation *)anInvocation

{

if ([someOtherObject respondsToSelector:[anInvocation selector]])

[anInvocation invokeWithTarget:someOtherObject];

else

[super forwardInvocation:anInvocation];

}

唯一參數(shù)是個(gè)NSInvocation類型對象,該對象封裝了原始的消息和消息的參數(shù),我們可以實(shí)現(xiàn)forwardInvocation:方法來對不能處理的消息做一些處理。也可以將消息轉(zhuǎn)發(fā)給其它對象處理,而不拋出錯(cuò)誤。

注意:在forwardInvocation:消息發(fā)送前,runtime系統(tǒng)會向?qū)ο蟀l(fā)送methodSignatureForSelector:消息,并取到返回的方法簽名用于生成NSInvocation對象。所以重寫forwardInvocation:的同時(shí)也要重寫methodSignatureForSelector:方法,否則會拋出異常。當(dāng)一個(gè)對象由于沒有相應(yīng)的方法實(shí)現(xiàn)而無法響應(yīng)某個(gè)消息時(shí),運(yùn)行時(shí)系統(tǒng)將通過forwardInvocation:消息通知該對象。每個(gè)對象都繼承了forwardInvocation:方法,我們可以將消息轉(zhuǎn)發(fā)給其它的對象。

- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector

{

NSMethodSignature* signature = [super methodSignatureForSelector:selector];

if (!signature) {

signature = [someObject methodSignatureForSelector:selector];

}

return signature;

}

forwardInvocation:方法就是一個(gè)不能識別消息的分發(fā)中心,將這些不能識別的消息轉(zhuǎn)發(fā)給不同的消息對象,或者轉(zhuǎn)發(fā)給同一個(gè)對象,再或者將消息翻譯成另外的消息,亦或者簡單的“吃掉”某些消息,因此沒有響應(yīng)也不會報(bào)錯(cuò)。例如:我們可以為了避免直接閃退,可以當(dāng)消息沒法處理時(shí)在這個(gè)方法中給用戶一個(gè)提示,也不失為一種友好的用戶體驗(yàn)。

轉(zhuǎn)發(fā)和多繼承

轉(zhuǎn)發(fā)和繼承相似,可用于為OC編程添加一些多繼承的效果,一個(gè)對象把消息轉(zhuǎn)發(fā)出去,就好像他把另一個(gè)對象中放法接過來或者“繼承”一樣。消息轉(zhuǎn)發(fā)彌補(bǔ)了objc不支持多繼承的性質(zhì),也避免了因?yàn)槎嗬^承導(dǎo)致單個(gè)類變得臃腫復(fù)雜。

雖然轉(zhuǎn)發(fā)可以實(shí)現(xiàn)繼承功能,但是NSObject還是必須表面上很嚴(yán)謹(jǐn),像respondsToSelector:和isKindOfClass:這類方法只會考慮繼承體系,不會考慮轉(zhuǎn)發(fā)鏈。

方法中的隱藏參數(shù)

我們經(jīng)常用到關(guān)鍵字self,但是self 是如何獲取當(dāng)前方法的對象呢?其實(shí)這也是runtime系統(tǒng)的作用,self是在方法運(yùn)行時(shí)被動態(tài)傳入的。

當(dāng)objc_msgSend找到方法對應(yīng)實(shí)現(xiàn)時(shí),它將直接調(diào)用該方法實(shí)現(xiàn),并將消息中的所有參數(shù)都傳遞給方法實(shí)現(xiàn),同時(shí)它還將傳遞兩個(gè)隱藏參數(shù):

1.接受消息的對象(self所指向的內(nèi)容,當(dāng)前方法的對象指針)

2.方法選擇器(_cmd指向的內(nèi)容,當(dāng)前方法的SEL指針)

因?yàn)樵谠创a方法的定義中,我們并沒有發(fā)現(xiàn)這兩個(gè)參數(shù)的聲明,它們是在代碼被編譯時(shí)被插入方法實(shí)現(xiàn)中的。盡管這些參數(shù)沒有被明確的聲明,在源代碼中我們?nèi)匀豢梢砸盟鼈儭?/p>

這兩個(gè)參數(shù)中self更實(shí)用,它是在方法實(shí)現(xiàn)中訪問消息接收者對象的實(shí)例變量的途徑。這時(shí)我們可能會想到另一個(gè)關(guān)鍵字super,實(shí)際上super關(guān)鍵字接收到消息時(shí),編譯器會創(chuàng)建一個(gè)objc_super結(jié)構(gòu)體:

struct objc_super { id receiver; Class class; };

這個(gè)結(jié)構(gòu)體指明了消息應(yīng)該被傳遞給特定的父類,receiver仍然是self本身,當(dāng)我們想通過[super class]獲取父類時(shí),編譯器其實(shí)是將指向self的id指針和class 的SEL傳遞給了objc_msgSendSuper 函數(shù)。只有在NSObject類中才能找到class 方法,然后class方法底層被轉(zhuǎn)化為object_getClass(),接著底層編譯器將代碼轉(zhuǎn)換為objc_msgSend(objc_super->receiver, @selector(class)) ,傳入的第一個(gè)參數(shù)是指向self的id指針,與調(diào)用[self class]相同,所以我們得到的永遠(yuǎn)都是self 的類型。因此:

這句話不能獲取父類的類型,只能獲取當(dāng)前類的類型名

?NSStringFromClass([super class])

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,789評論 0 9
  • 本文轉(zhuǎn)載自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex閱讀 799評論 0 1
  • 消息發(fā)送和轉(zhuǎn)發(fā)流程可以概括為:消息發(fā)送(Messaging)是 Runtime 通過 selector 快速查找 ...
    lylaut閱讀 1,898評論 2 3
  • 參考鏈接: http://www.cnblogs.com/ioshe/p/5489086.html 簡介 Runt...
    樂樂的簡書閱讀 2,165評論 0 9
  • 時(shí)間真快,依稀記得剛見你們時(shí)的樣子,青澀而懵懂,對在這個(gè)陌生的環(huán)境中的生活充滿著期待。經(jīng)過時(shí)間的打磨,歲月的洗禮,...
    想飛的蝸牛閱讀 332評論 0 0