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

Objective-C 是一個動態(tài)語言,可以通過運行時系統(tǒng)來動態(tài)得創(chuàng)建類和對象吕世、進行消息傳遞和轉(zhuǎn)發(fā)彰触。

在Objective-C中,[object run]語法并不會立即執(zhí)行 run 這個方法的代碼命辖。它是在運行時給 object 發(fā)送一條叫 run 的消息况毅。這個消息分蓖,也許會由 object 來處理,也許會被轉(zhuǎn)發(fā)給另一個對象尔许,或者不予理睬假裝沒收到這個消息么鹤。多條不同的消息也可以對應(yīng)同一個方法實現(xiàn)。這些都是在程序運行的時候決定的味廊。

事實上蒸甜,在編譯時你寫的 Objective-C 函數(shù)調(diào)用的語法都會被翻譯成一個 C 的函數(shù)調(diào)用objc_msgSend()

比如,下面兩行代碼就是等價的:

[array insertObject:foo atIndex:5];

objc_msgSend(array,@selector(insertObject:atIndex:), foo, 5)

在 Objective-C 中余佛,類柠新、對象和方法都是一個 C 的結(jié)構(gòu)體,從objc/objc.h頭文件中辉巡,我們可以找到他們的定義:

structobjc_class {

Class isa? OBJC_ISA_AVAILABILITY;

#if !__OBJC2__

Class super_class??????????????????????????????????????? OBJC2_UNAVAILABLE;

constchar*name ??????????????????????????????????????? OBJC2_UNAVAILABLE;

longversion ??????????????????????????????????????????? OBJC2_UNAVAILABLE;

longinfo??????????????????????????????????????????????? OBJC2_UNAVAILABLE;

longinstance_size ????????????????????????????????????? OBJC2_UNAVAILABLE;

structobjc_ivar_list *ivars ??????????????????????????? OBJC2_UNAVAILABLE;

structobjc_method_list **methodLists??????????????????? OBJC2_UNAVAILABLE;

structobjc_cache *cache ??????????????????????????????? OBJC2_UNAVAILABLE;

structobjc_protocol_list *protocols ??????????????????? OBJC2_UNAVAILABLE;

#endif

} OBJC2_UNAVAILABLE;

structobjc_object {

Class isa? OBJC_ISA_AVAILABILITY;

};

OBJC2_UNAVAILABLE;

structobjc_method_list {

structobjc_method_list *obsolete??????????????????????? OBJC2_UNAVAILABLE;

intmethod_count ??????????????????????????????????????? OBJC2_UNAVAILABLE;

#ifdef __LP64__

intspace??????????????????????????????????????????????? OBJC2_UNAVAILABLE;

#endif

/* variable length structure */

structobjc_method method_list[1]??????????????????????? OBJC2_UNAVAILABLE;

}

structobjc_method {

SELmethod_name????????????????????????????????????????? OBJC2_UNAVAILABLE;

char*method_types ????????????????????????????????????? OBJC2_UNAVAILABLE;

IMPmethod_imp ????????????????????????????????????????? OBJC2_UNAVAILABLE;

}

在這里可以看到恨憎,在一個類中,有父類的指針郊楣,類名憔恳,版本的信息。

ivars是objc_ivar_list成員變量列表的指針净蚤;methodLists是指向objc_method_list指針的指針钥组。*methodLists是指向方法列表的指針。

objc_method_list本質(zhì)是一個有objc_method元素的可變長度的數(shù)組塞栅。一個objc_method結(jié)構(gòu)體中有函數(shù)名者铜,也就是SEL腔丧,有表示函數(shù)類型的字符串?放椰,以及函數(shù)的實現(xiàn)IMP。

從這些定義中可以看出發(fā)送一條消息也就objc_msgSend做了什么事愉粤。舉objc_msgSend(obj, run)這個例子來說:

首先砾医,通過 obj 的 isa 指針找到它的 class ;

在 class 的 method list 找 run ;

如果 class 中沒到 run,繼續(xù)往它的 superclass 中找 ;

一旦找到 run 這個函數(shù)衣厘,就去執(zhí)行它的實現(xiàn)IMP.

再找到 run 之后如蚜,把 run 的method_name作為 key ,method_imp作為 value 給存到objc_cache影暴。當(dāng)再次收到 run 消息的時候错邦,可以直接在 cache 里找到,避免去遍歷objc_method_list.

動態(tài)方法解析和轉(zhuǎn)發(fā)

在上面的例子中型宙,如果run沒有找到會發(fā)生什么撬呢?

當(dāng)向someObject發(fā)送某消息,但runtime system在當(dāng)前類和父類中都找不到對應(yīng)方法的實現(xiàn)時妆兑,runtime system并不會立即報錯使程序崩潰魂拦,而是依次執(zhí)行下列步驟:

1.Method Resolution:向當(dāng)前類發(fā)送resolveInstanceMethod:信號毛仪,檢查是否動態(tài)向該類添加了方法。

2.Fast forwarding:檢查該類是否實現(xiàn)了forwardingTargetForSelector:方法芯勘,若實現(xiàn)了則調(diào)用這個方法箱靴。若該方法返回值對象非nil或非self,則向該返回對象重新發(fā)送消息荷愕。

3.Normal Fowarding:runtime發(fā)送methodSignatureForSelector:消息獲取Selector對應(yīng)的方法簽名衡怀。返回值非空則通過forwardInvocation:轉(zhuǎn)發(fā)消息,返回值為空則向當(dāng)前對象發(fā)送doesNotRecognizeSelector:消息安疗,程序崩潰退出

Method Resolution

首先狈癞,Objective-C 運行時會調(diào)用+resolveInstanceMethod:或者+resolveClassMethod:,讓你有機會提供一個函數(shù)實現(xiàn)茂契。如果你添加了函數(shù)并返回 YES蝶桶, 那運行時系統(tǒng)就會重新啟動一次消息發(fā)送的過程卸留。以run為例会放,你可以這么實現(xiàn):

voidrun (idself,SEL_cmd){

NSLog(@"%@ %s",self,sel_getName(_cmd));

}

+ (BOOL)resolveInstanceMethod:(SEL)sel{

if(sel ==@selector(run)) {

class_addMethod(self,sel, (IMP)run,"v@:");

returnYES;

}

return[superresolveClassMethod:sel];

}

如果 resolve 方法返回 NO 节沦,運行時就會移到下一步:消息轉(zhuǎn)發(fā)(Message Forwarding)

Fast forwarding

如果目標(biāo)對象實現(xiàn)了-forwardingTargetForSelector:让腹,Runtime 這時就會調(diào)用這個方法募狂,給你把這個消息轉(zhuǎn)發(fā)給其他對象的機會衫嵌。

- (id)forwardingTargetForSelector:(SEL)aSelector

{

if([doctorrespondsToSelector:aSelector]) {

returndoctor;

}

return[superforwardingTargetForSelector:aSelector];

}

只要這個方法返回的不是 nil 和 self晃听,整個消息發(fā)送的過程就會被重啟唇聘,當(dāng)然發(fā)送的對象會變成你返回的那個對象璧亚。否則讨韭,就會繼續(xù) Normal Fowarding 。

Normal forwarding

這一步是 Runtime 最后一次給你挽救的機會癣蟋。首先它會發(fā)送-methodSignatureForSelector:消息獲得函數(shù)的參數(shù)和返回值類型透硝。如果-methodSignatureForSelector:返回 nil ,Runtime 則會發(fā)出-doesNotRecognizeSelector:消息疯搅,程序這時也就掛掉了濒生。如果返回了一個函數(shù)簽名,Runtime 就會創(chuàng)建一個 NSInvocation 對象并發(fā)送-forwardInvocation:消息給目標(biāo)對象幔欧。

NSInvocation 實際上就是對一個消息的描述罪治,包括selector 以及參數(shù)等信息。所以你可以在-forwardInvocation:里修改傳進來的 NSInvocation 對象礁蔗,然后發(fā)送-invokeWithTarget:消息給它觉义,傳進去一個新的目標(biāo):

- (NSMethodSignature*)methodSignatureForSelector:(SEL)sel{

return[NSMethodSignaturesignatureWithObjCTypes:"v@:"];

}

- (void)forwardInvocation:(NSInvocation*)invocation{

[invocationinvokeWithTarget:self.object];

}

這里解釋一下"v@:”,每一個方法會默認(rèn)隱藏兩個參數(shù)浴井,self晒骇、_cmd,self代表方法調(diào)用者,_cmd代表這個方法的SEL厉碟,簽名類型就是用來描述這個方法的返回值喊巍、參數(shù)的,v代表返回值為void箍鼓,@表示self崭参,:表示_cmd。

總結(jié)

Objective-C 中給一個對象發(fā)送消息會經(jīng)過以下幾個步驟:

在對象類的method_list中嘗試找到該消息款咖。如果找到了何暮,跳到相應(yīng)的函數(shù)IMP去執(zhí)行實現(xiàn)代碼;

如果沒有找到铐殃,Runtime 會發(fā)送+resolveInstanceMethod:或者+resolveClassMethod:嘗試去 resolve 這個消息海洼;

如果 resolve 方法返回 NO,Runtime 就發(fā)送-forwardingTargetForSelector:允許你把這個消息轉(zhuǎn)發(fā)給另一個對象富腊;

如果沒有新的目標(biāo)對象返回坏逢, Runtime 就會發(fā)送-methodSignatureForSelector:和-forwardInvocation:消息。你可以發(fā)送-invokeWithTarget:消息來手動轉(zhuǎn)發(fā)消息或者發(fā)送-doesNotRecognizeSelector:拋出異常赘被。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末是整,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子民假,更是在濱河造成了極大的恐慌浮入,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件羊异,死亡現(xiàn)場離奇詭異事秀,居然都是意外死亡,警方通過查閱死者的電腦和手機野舶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門易迹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人筒愚,你說我怎么就攤上這事赴蝇∑姓悖” “怎么了巢掺?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長劲蜻。 經(jīng)常有香客問我陆淀,道長,這世上最難降的妖魔是什么先嬉? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任轧苫,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘含懊。我一直安慰自己身冬,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布岔乔。 她就那樣靜靜地躺著酥筝,像睡著了一般。 火紅的嫁衣襯著肌膚如雪雏门。 梳的紋絲不亂的頭發(fā)上嘿歌,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天,我揣著相機與錄音茁影,去河邊找鬼宙帝。 笑死,一個胖子當(dāng)著我的面吹牛募闲,可吹牛的內(nèi)容都是我干的步脓。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼浩螺,長吁一口氣:“原來是場噩夢啊……” “哼沪编!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起年扩,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤蚁廓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后厨幻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體相嵌,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年况脆,在試婚紗的時候發(fā)現(xiàn)自己被綠了饭宾。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡格了,死狀恐怖看铆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情盛末,我是刑警寧澤弹惦,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站悄但,受9級特大地震影響棠隐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜檐嚣,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一助泽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦嗡贺、人聲如沸隐解。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽厢漩。三九已至,卻和暖如春岩臣,著一層夾襖步出監(jiān)牢的瞬間溜嗜,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工架谎, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留炸宵,地道東北人。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓谷扣,卻偏偏與公主長得像土全,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子会涎,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,627評論 2 350

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉裹匙,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,690評論 0 9
  • 本文轉(zhuǎn)載自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex閱讀 751評論 0 1
  • runtime 和 runloop 作為一個程序員進階是必須的,也是非常重要的末秃, 在面試過程中是經(jīng)常會被問到的概页, ...
    SOI閱讀 21,791評論 3 63
  • runtime 和 runloop 作為一個程序員進階是必須的,也是非常重要的练慕, 在面試過程中是經(jīng)常會被問到的惰匙, ...
    made_China閱讀 1,206評論 0 7
  • “不經(jīng)歷風(fēng)雨,怎可見彩虹?”小拙打小自然課就學(xué)得亂七八糟铃将,從來就弄不明白什么風(fēng)呀雨呀為啥會形成项鬼。自然課不是主課,父...
    小拙花生豆閱讀 378評論 0 1