iOS runtime之消息發(fā)送爽室,消息轉(zhuǎn)發(fā)機制

消息:消息直到運行時才會與方法實現(xiàn)進行綁定温自。在OC中方法調(diào)用是一個消息發(fā)送的過程。

在OC中調(diào)用一個方法的格式如:

[cat eat: fish];

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

objc_msgSend(cat, @selector(eat:), fish);

那么在這個C語言函數(shù)中發(fā)生了什么事情涩赢?編譯器是如何找到這個方法的呢戈次?消息發(fā)送的主要步驟如下:

1.首先檢查這個selector是不是要忽略。比如Mac OS X開發(fā)筒扒,有了垃圾回收就不會理會retain怯邪,release這些函數(shù)。
2.檢測這個selector的target是不是nil花墩,OC允許我們對一個nil對象執(zhí)行任何方法不會Crash悬秉,因為運行時會被忽略掉。
3.如果上面兩步都通過了冰蘑,就開始查找這個類的實現(xiàn)IMP和泌,先從cache里查找,如果找到了就運行對應(yīng)的函數(shù)去執(zhí)行相應(yīng)的代碼祠肥。
4.如果cache中沒有找到就找類的方法列表中是否有對應(yīng)的方法武氓。
5.如果類的方法列表中找不到就到父類的方法列表中查找,一直找到NSObject類為止仇箱。
6.如果還是沒找到就要開始進入動態(tài)方法解析聋丝,后面會說

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

動態(tài)方法解析

當(dāng)runtime系統(tǒng)在Cache和類的方法列表(包括父類)中找不到要執(zhí)行的方法時runtime會調(diào)用resolveInstanceMethod: 或者resolveClassMethod: 來給我們一次動態(tài)添加方法實現(xiàn)的機會。我們需要用class_addMethod函數(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 這個方法添加了實現(xiàn)內(nèi)容奔垦,就是dynamicMethodIMP方法中的代碼屹耐。其中"v@:"表示返回值跟參數(shù)。注意:動態(tài)方法解析是在上面說的第6步后椿猎,消息轉(zhuǎn)發(fā)機制侵入之前執(zhí)行惶岭,動態(tài)方法解析器將會首先給予提供該方法選擇器對應(yīng)的IMP的機會寿弱。如果你想讓該方法選擇器被傳送到轉(zhuǎn)發(fā)機制,就讓resolveInstanceMethod:方法返回NO按灶。

消息轉(zhuǎn)發(fā)

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

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if(aSelector == @selector(mysteriousMethod:)){
          return alternateObject;
    }
    return [super forwardingTargetForSelector:aSelector];
}

如果此方法返回的是nil 或者self,則會進入消息轉(zhuǎn)發(fā)機制(forwardInvocation:)噪矛,否則將會向返回的對象重新發(fā)送消息。
2.轉(zhuǎn)發(fā)
當(dāng)動態(tài)方法解析不做處理返回NO時铺罢,則會觸發(fā)消息轉(zhuǎn)發(fā)機制艇挨。這時forwardInvocation:方法會被執(zhí)行,我們可以重寫這個方法來自定義我們的轉(zhuǎn)發(fā)邏輯:

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    if ([someOtherObject respondsToSelector:[anInvocation selector]])
        [anInvocation invokeWithTarget:someOtherObject];
    else
        [super forwardInvocation:anInvocation];
}

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

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

- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
    NSMethodSignature* signature = [super methodSignatureForSelector:selector];
    if (!signature) {
       signature = [someObject methodSignatureForSelector:selector];
    }
    return signature;
}

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

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

轉(zhuǎn)發(fā)和繼承相似傀履,可用于為OC編程添加一些多繼承的效果虱朵,一個對象把消息轉(zhuǎn)發(fā)出去,就好像他把另一個對象中放法接過來或者“繼承”一樣。消息轉(zhuǎn)發(fā)彌補了objc不支持多繼承的性質(zhì)碴犬,也避免了因為多繼承導(dǎo)致單個類變得臃腫復(fù)雜絮宁。
雖然轉(zhuǎn)發(fā)可以實現(xiàn)繼承功能,但是NSObject還是必須表面上很嚴(yán)謹(jǐn)翅敌,像respondsToSelector:和isKindOfClass:這類方法只會考慮繼承體系羞福,不會考慮轉(zhuǎn)發(fā)鏈惕蹄。

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

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

當(dāng)objc_msgSend找到方法對應(yīng)實現(xiàn)時遭顶,它將直接調(diào)用該方法實現(xiàn),并將消息中的所有參數(shù)都傳遞給方法實現(xiàn)泪蔫,同時它還將傳遞兩個隱藏參數(shù):
1.接受消息的對象(self所指向的內(nèi)容棒旗,當(dāng)前方法的對象指針)
2.方法選擇器(_cmd指向的內(nèi)容,當(dāng)前方法的SEL指針)

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

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

struct objc_super { id receiver; Class class; };

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

// 這句話不能獲取父類的類型,只能獲取當(dāng)前類的類型名                     
NSLog(@"%@", NSStringFromClass([super class]));
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末婆咸,一起剝皮案震驚了整個濱河市竹捉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌尚骄,老刑警劉巖块差,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡憨闰,警方通過查閱死者的電腦和手機状蜗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鹉动,“玉大人轧坎,你說我怎么就攤上這事≡笫荆” “怎么了缸血?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長械筛。 經(jīng)常有香客問我捎泻,道長,這世上最難降的妖魔是什么埋哟? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任笆豁,我火速辦了婚禮,結(jié)果婚禮上赤赊,老公的妹妹穿的比我還像新娘闯狱。我一直安慰自己,他們只是感情好抛计,可當(dāng)我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布哄孤。 她就那樣靜靜地躺著,像睡著了一般爷辱。 火紅的嫁衣襯著肌膚如雪录豺。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天饭弓,我揣著相機與錄音双饥,去河邊找鬼。 笑死弟断,一個胖子當(dāng)著我的面吹牛咏花,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播阀趴,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼昏翰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了刘急?” 一聲冷哼從身側(cè)響起棚菊,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎叔汁,沒想到半個月后统求,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體检碗,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年码邻,在試婚紗的時候發(fā)現(xiàn)自己被綠了折剃。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡像屋,死狀恐怖怕犁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情己莺,我是刑警寧澤奏甫,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站篇恒,受9級特大地震影響扶檐,放射性物質(zhì)發(fā)生泄漏凶杖。R本人自食惡果不足惜胁艰,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望智蝠。 院中可真熱鬧腾么,春花似錦、人聲如沸杈湾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽漆撞。三九已至殴泰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間浮驳,已是汗流浹背悍汛。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留至会,地道東北人离咐。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像奉件,于是被迫代替她去往敵國和親宵蛀。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,884評論 2 354

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