【iOS_Development】消息轉(zhuǎn)發(fā)機(jī)制簡述

前言

在Objective-C中,如果某對象傳遞消息扶认,那就會使用動態(tài)綁定機(jī)制來決定需要調(diào)用的方法;在底層殊橙,所有方法都是普通的C語言函數(shù)辐宾,然而對象接收消息之后,究竟該調(diào)用哪個方法則完全取決于運(yùn)行期決定膨蛮,設(shè)置可以在程序運(yùn)行時改變叠纹,這些特性使得Objective-C成為一門真正的動態(tài)語言。

發(fā)消息

給對象發(fā)送消息可以這樣來寫

id returnValue = [someObject messageName:param];

其原型如下

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

故編譯器會將其轉(zhuǎn)換為如下函數(shù)

id returnValue = objc_msgSend(someObject, @selector(messageName:), param);

objc_msgSend函數(shù)會依據(jù)接收者(returnValue)與選擇子(messageName)的類型來調(diào)用適當(dāng)?shù)姆椒ǔǜ穑粸榱送瓿纱瞬僮饔欤摲椒ㄐ枰诮邮照咚鶎俚念愔兴褜て?strong>方法列表,如果能找到與選擇子名稱相符的方法惹谐,就跳至其實現(xiàn)代碼持偏;若是找不到,那就沿著繼承體系繼續(xù)向上查找氨肌,等找到合適的方法之后再跳轉(zhuǎn)鸿秆;若最終還是找不到相符的方法,那就執(zhí)行消息轉(zhuǎn)發(fā)操作怎囚。

消息調(diào)用過程還會存在如下邊界情況
  a. objc_msgSend_stret:如果待發(fā)送的消息要返回結(jié)構(gòu)體谬莹,那么可交由此函數(shù)處理。只有當(dāng)CPU寄存器能夠容納得下消息返回類型時桩了,這個函數(shù)才能處理此消息,否則埠戳,交由另一個函數(shù)執(zhí)行派發(fā)井誉。此時,那個函數(shù)會通過分配在棧上的某個變量來處理消息所返回的結(jié)構(gòu)體
  b. objc_msgSend_fpret:如果消息返回的是浮點數(shù)整胃,那么可交由此函數(shù)處理颗圣,在某些架構(gòu)的CPU中調(diào)用函數(shù)時,需要對“浮點寄存器”做特殊處理,也就是說在岂,通常所用的objc_msgSend在這種情況下并不合適奔则。這個函數(shù)是為了處理x86等架構(gòu)CPU中某些令人稍覺驚訝的奇怪情況
  c. objc_msgSendSuper:如果給超類發(fā)消息,例如[super message:parameter]蔽午,那么就交由此函數(shù)處理易茬。也有另外兩個與objc_msgSend_stret和objc_msgSend_fpret等效的函數(shù),用于處理發(fā)給super的相應(yīng)消息


消息轉(zhuǎn)發(fā)三部曲

若想令類能理解某條消息及老,我們必須以程序碼實現(xiàn)出對應(yīng)的方法才行抽莱;但是,在編譯期向類發(fā)送了其無法解讀的消息并不會報錯骄恶,因為在運(yùn)行期可以繼續(xù)向類中添加方法食铐,所以編譯器在編譯時還無法確知類中到底會不會有某個方法實現(xiàn)(即編譯器無法確定某類型對象到底能解讀多少種選擇子,因為運(yùn)行期還可以向其中動態(tài)新增)僧鲁。當(dāng)對象接收到無法解讀的消息后虐呻,就會啟動“消息轉(zhuǎn)發(fā)”機(jī)制,程序員可經(jīng)由此過程告訴對象應(yīng)如何處理未知消息

  • 消息轉(zhuǎn)發(fā)分為兩大階段
    ??第一階段先征詢接收者寞秃,所屬的類斟叼,看其能否動態(tài)添加方法,以處理當(dāng)前這個“未知的選擇子”蜕该,這叫做“動態(tài)方法解析

    ??第二階段涉及“完整的消息轉(zhuǎn)發(fā)機(jī)制”犁柜,如果運(yùn)行期系統(tǒng)已經(jīng)把第一階段執(zhí)行完了,那么接收者就無法再以動態(tài)新增方法的手段來相應(yīng)包含該選擇子的消息了堂淡。此時運(yùn)行期系統(tǒng)會請求接收者以其他手段來處理與消息相關(guān)的方法調(diào)用馋缅。這又細(xì)分為兩小步:
    ????1). 首先請接收者看看有沒有其他對象能處理這條消息,若有绢淀,則運(yùn)行期系統(tǒng)會把這條消息轉(zhuǎn)給那個對象萤悴,于是消息轉(zhuǎn)發(fā)過程結(jié)束,一切如常
    ????2). 若沒有“備援接收者” 皆的,則啟動完整的消息轉(zhuǎn)發(fā)機(jī)制覆履,運(yùn)行期系統(tǒng)會把與消息有關(guān)的全部細(xì)節(jié)都封裝到NSInvocation對象中,再給接收者最后一次機(jī)會费薄,令其設(shè)法解決當(dāng)前還未處理的這條消息

第一步:動態(tài)方法解析

對象接收到無法解讀的消息后硝全,首先將調(diào)用其所屬類的以下方法(如果類無法立即響應(yīng)某個選擇子,那么就會啟動消息轉(zhuǎn)發(fā)流程)

// 未知選擇子為實例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel

// 未知選擇子為類方法
+ (BOOL)resolveClassMethod:(SEL)sel
  • 使用這種方法的前提是:相關(guān)方法的實現(xiàn)代碼已經(jīng)寫好楞抡,只等著運(yùn)行時候動態(tài)插在類里面就可以了
  • 運(yùn)行期添加方法時使用此函數(shù)

使用示例如下

.h
@property (nonatomic, strong) NSString *strTest;

.m
@dynamic strTest;

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSString *selectorString = NSStringFromSelector(sel);
    NSLog(@"selectorString ==> %@", selectorString);
    
    if ([selectorString hasPrefix:@"set"]) {
        // 所添加的方法使用純C函數(shù)實現(xiàn)的
        // (IMP)autoautoDictionarySetter:函數(shù)指針伟众,指向待添加的方法
        // "v@:@“:待添加方法的類型編碼,本例中召廷,開頭字符表示方法的返回值類型凳厢,后續(xù)字符表示其所接受的各個參數(shù)
        // class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method))
        class_addMethod(self, sel, (IMP)autoautoDictionarySetter, "v@:@");  
    } else {
        class_addMethod(self, sel, (IMP)autoDictionaryGetter, "@@:");
    }
    return YES;
}

id autoDictionaryGetter(id self, SEL _cmd) {
    return @"getter";
}

void autoautoDictionarySetter(id self, SEL _cmd, id value) {
    NSLog(@"Setter");
}


第二步:備援接收者

假使動態(tài)方法解析中未能夠處理未知選擇子账胧,接下來運(yùn)行期系統(tǒng)會問接收者能不能把這條消息轉(zhuǎn)給其他接收者來處理,即調(diào)用以下方法

// 返回可以處理未知選擇子的對象(當(dāng)返回非self\非nil時先紫,消息被轉(zhuǎn)給新對象執(zhí)行)
- (id)forwardingTargetForSelector:(SEL)aSelector
  • 若想在發(fā)送給備援接收者之前先修改消息內(nèi)容治泥,那就得通過完整的消息轉(zhuǎn)發(fā)機(jī)制來做
  • 轉(zhuǎn)發(fā)給另一個對象、改變方法時使用此函數(shù)

第三步:完整的消息轉(zhuǎn)發(fā)

如果轉(zhuǎn)發(fā)算法來到完整的消息轉(zhuǎn)發(fā)這一步的話遮精,那么唯一能做的就是啟用完整的消息轉(zhuǎn)發(fā)機(jī)制了居夹;首先創(chuàng)建NSInvocation對象,把尚未處理的那條消息有關(guān)的全部細(xì)節(jié)都封于其中仑鸥;此對象包含選擇子吮播、目標(biāo)及參數(shù),再觸發(fā)NSInvocation對象時眼俊,“消息派發(fā)系統(tǒng)”將親自出馬意狠,把消息指派給目標(biāo)對象,此時會調(diào)用以下函數(shù)

- (void)forwardInvocation:(NSInvocation *)anInvocation

這個方法實現(xiàn)的很簡單:只需修改調(diào)用目標(biāo)疮胖,使消息在新目標(biāo)上得以調(diào)用即可环戈;然而這樣實現(xiàn)出來的方法與“備援接收者”方案實現(xiàn)的方法等效,所以很少有人采用這么簡單的實現(xiàn)方式澎灸;比較有用的實現(xiàn)方式為:在觸發(fā)消息之前院塞,先以某種方式改變消息內(nèi)容,比如追加另外一個參數(shù)性昭,或是改換選擇子等等


??實現(xiàn)此方法時拦止,若發(fā)現(xiàn)某調(diào)用操作不應(yīng)由本類處理,則需調(diào)用超類的同名方法糜颠;這樣的話汹族,繼承體系中的每個類都有機(jī)會處理此調(diào)用請求,直至NSObject其兴;最后調(diào)用了NSObject類的方法顶瞒,那么該方法還會繼續(xù)調(diào)用“doesNotRecongizeSelector:”以拋出異常,此異常表明選擇子最終未能得到處理

  • 需要轉(zhuǎn)發(fā)給多個對象時使用此函數(shù)

為了更好的去理解整個過程可以參看以下此流程圖



總結(jié)

接收者在每一步中均有機(jī)會處理消息元旬,步驟越往后榴徐,消息處理的代價就越大,最好能在第一步就處理完匀归,這樣的話坑资,運(yùn)行期系統(tǒng)就可以將此方法緩存起來,如果這個類的實例稍后還收到同名選擇子穆端,那么根本就無須啟動消息轉(zhuǎn)發(fā)流程盐茎。若想在第三步里將消息轉(zhuǎn)給備援接收者,那還不如把轉(zhuǎn)發(fā)操作提前到第二步徙赢,因為第三步只是修改了調(diào)用目標(biāo)字柠,這項改動放到第二步執(zhí)行更為簡單,不然的話狡赐,還得創(chuàng)建完整的NSInvocation


聲明:本文參考自《Effective Objective C 2.0:編寫高質(zhì)量iOS與OS X代碼的52個有效方法》


GitHub主頁

CSDN Blog

Email:jinjob@icloud.com

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末窑业,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子枕屉,更是在濱河造成了極大的恐慌常柄,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搀擂,死亡現(xiàn)場離奇詭異西潘,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)哨颂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門喷市,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人威恼,你說我怎么就攤上這事品姓。” “怎么了箫措?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵腹备,是天一觀的道長。 經(jīng)常有香客問我斤蔓,道長植酥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任弦牡,我火速辦了婚禮友驮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘喇伯。我一直安慰自己喊儡,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布稻据。 她就那樣靜靜地躺著艾猜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪捻悯。 梳的紋絲不亂的頭發(fā)上匆赃,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天,我揣著相機(jī)與錄音今缚,去河邊找鬼算柳。 笑死,一個胖子當(dāng)著我的面吹牛姓言,可吹牛的內(nèi)容都是我干的瞬项。 我是一名探鬼主播蔗蹋,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼囱淋!你這毒婦竟也來了猪杭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤妥衣,失蹤者是張志新(化名)和其女友劉穎皂吮,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體税手,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡蜂筹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了芦倒。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片艺挪。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖熙暴,靈堂內(nèi)的尸體忽然破棺而出闺属,到底是詐尸還是另有隱情,我是刑警寧澤周霉,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布掂器,位于F島的核電站,受9級特大地震影響俱箱,放射性物質(zhì)發(fā)生泄漏国瓮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一狞谱、第九天 我趴在偏房一處隱蔽的房頂上張望乃摹。 院中可真熱鬧,春花似錦跟衅、人聲如沸孵睬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽掰读。三九已至,卻和暖如春叭莫,著一層夾襖步出監(jiān)牢的瞬間蹈集,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工雇初, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留拢肆,地道東北人。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像郭怪,于是被迫代替她去往敵國和親支示。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,077評論 2 355

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理移盆,服務(wù)發(fā)現(xiàn)悼院,斷路器,智...
    卡卡羅2017閱讀 134,672評論 18 139
  • 目錄 Objective-C Runtime到底是什么 Objective-C的元素認(rèn)知 Runtime詳解 應(yīng)用...
    Ryan___閱讀 1,939評論 1 3
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉咒循,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,721評論 0 9
  • 前言 Objective-C是一門非常動態(tài)的語言,以至于確定調(diào)用哪個方法被推遲到了運(yùn)行時绞愚,而非編譯時叙甸。與之相反,C...
    VV木公子閱讀 6,196評論 3 31
  • 6.理解“屬性”這一概念 @dynamic關(guān)鍵字:告訴編譯器不要自動創(chuàng)建實現(xiàn)屬性所用的實例變量僚祷,也不要為其創(chuàng)建存取...
    Code_Ninja閱讀 724評論 0 3