OC的Runtime機(jī)制之消息

ObjC的方法調(diào)用都是動(dòng)態(tài)的,這點(diǎn)和其他的語言是有區(qū)別的枚赡,為了更深層次理解動(dòng)態(tài)的概念氓癌,我們必須先知道 Class,SEL,IMP這三個(gè)概念贫橙。

typedef struct objc_class *Class; typedef struct objc_object { 
Class isa; } *id; 
typedef struct objc_selector *SEL; typedef id (*IMP)(id, SEL, ...); 

class 的含義我們之前講個(gè)贪婉,他被定義為一個(gè)指向obic_class的結(jié)構(gòu)體指針,這個(gè)結(jié)構(gòu)體的定義如下

struct objc_class {
struct objc_class* isa;struct objc_class super_class; /*父類*/ 
const char *name; /*類名字*/ long version; /*版本信息*/ 
long info; /*類信息*/long instance_size; /*實(shí)例大小*/struct objc_ivar_list *ivars; /*實(shí)例參數(shù)鏈表*/ 
struct objc_method_list **methodLists; /*方法鏈表*/ 
struct objc_cache *cache; /*方法緩存*/ 
struct objc_protocol_list *protocols; /*協(xié)議鏈表*/ 
}

有此可見卢肃,Class是指向類結(jié)構(gòu)體的指針疲迂,改結(jié)構(gòu)體含有一個(gè)指向其父類類結(jié)構(gòu)的指針,該類的名稱践剂,實(shí)例大小等其他信息鬼譬。
NSObject 的 class 方法就返回這樣一個(gè)指向其類結(jié)構(gòu)的指針。每一個(gè)類實(shí)例對(duì)象的第一個(gè)實(shí)例變量是一 個(gè)指向該對(duì)象的類結(jié)構(gòu)指針逊脯,叫做isa优质,通過isa,對(duì)象可以訪問它對(duì)應(yīng)的類军洼,以及父類巩螃。實(shí)例對(duì)象的第一個(gè)實(shí)例變量為 isa,它指向該類的類結(jié)構(gòu) The object’s class匕争。 而該類結(jié)構(gòu)有一個(gè)指向其父類類結(jié)構(gòu)的指針 superclass避乏, 以及自身消息名稱(selector)/實(shí)現(xiàn)地址(address) 的方法鏈表。如下圖

messaging1.gif

方法的含義:
這里所說的方法鏈表里面儲(chǔ)存的是Method類型的甘桑。圖中selector就是指Method的SEL拍皮,address就是address 就是指 Method 的 IMP 。
Method 的結(jié)構(gòu)如下

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}

一個(gè)方法 Method跑杭,其包含一個(gè)方法選標(biāo) SEL – 表示該方法的名稱铆帽,一個(gè) types – 表示該方法參數(shù)的類型, 一個(gè) IMP - 指向該方法的具體實(shí)現(xiàn)的函數(shù)指針德谅。
SEL的含義:
SEL的定義為:typedef struct objc_selector *SEL;
不同的類可以擁有相同的 selector爹橱,這個(gè)沒有問題,因?yàn)椴煌惖膶?shí)例對(duì)象 performSelector 相同的 selector 時(shí)窄做,會(huì)在各自的消息選標(biāo)(selector)/實(shí)現(xiàn)地址(address) 方法鏈表中根據(jù) selector 去查找具體的 方法實(shí)現(xiàn) IMP, 然后用這個(gè)方法實(shí)現(xiàn)去執(zhí)行具體的實(shí)現(xiàn)代碼愧驱。這是一個(gè)動(dòng)態(tài)綁定的過程,在編譯的時(shí)候椭盏, 我們不知道最終會(huì)執(zhí)行哪一些代碼组砚,只有在執(zhí)行的時(shí)候,通過 selector 去查詢庸汗,我們才能確定具體的執(zhí)行 代碼惫确。
方法列表的定義

//方法列表
struct objc_method_list {
    struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;

    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}  

IMP 的含義:在前面我們也看到 IMP 的定義為: typedef id (*IMP)(id, SEL, ...);
IMP 是一個(gè)函數(shù)指針,這個(gè)被指向的函數(shù)包含一個(gè)接收消息的 對(duì)象 id(self 指針), 調(diào)用方法的選標(biāo) SEL (方法名)蚯舱,以及不定個(gè)數(shù)的方法參數(shù)改化,并返回一個(gè) id。也就是說 IMP 是消息最終調(diào)用的執(zhí)行代碼枉昏,是方法真正的實(shí)現(xiàn)代碼 陈肛。我們可以像在C語言里面一樣使用這個(gè)函數(shù) 指針。
NSObject 類中的 methodForSelector:方法就是這樣一個(gè)獲取指向方法實(shí)現(xiàn) IMP 的指針兄裂, methodForSelector:返回的指針和賦值的變量類型必須完全一致句旱,包括方法的參數(shù)類型和返回值類型。
使用 methodForSelector:來避免動(dòng)態(tài)綁定將減少大部分消息的開銷晰奖,但是這只有在指定的消息被重復(fù)發(fā) 送很多次時(shí)才有意義谈撒,例如上面的 for 循環(huán)。
注意匾南,methodForSelector:是 Cocoa 運(yùn)行時(shí)系統(tǒng)的提供的功能啃匿,而不是 Objective-C 語言本身的功能。

下面我們總結(jié)一下:
1.一個(gè)實(shí)例對(duì)象的第一個(gè)實(shí)例變量是isa蛆楞,這個(gè)isa指向該對(duì)象的類結(jié)構(gòu)溯乒,類結(jié)構(gòu)有指向父類類結(jié)構(gòu)。
2.在元類中是儲(chǔ)存類方法的豹爹,方法鏈表里儲(chǔ)存的是Mehtod類型的裆悄。SEL代表改方法的名稱,IMP代表該方法具體實(shí)現(xiàn)的函數(shù)指針臂聋。
3.當(dāng)實(shí)例對(duì)象查找方法時(shí)光稼,會(huì)在方法列表中根據(jù)SEL去查找具體的實(shí)現(xiàn)的IMP,根據(jù)IMP這個(gè)指針然后找到具體實(shí)現(xiàn)的代碼孩等,這個(gè)是一個(gè)動(dòng)態(tài)綁定的過程艾君。
消息調(diào)用過程:
先舉出一個(gè)例子

Cat * cat = [[Cat alloc] init]; 
[cat eat]; 

消息函數(shù) obj_msgSend:當(dāng)調(diào)用方法的時(shí)候,編譯器會(huì)將消息轉(zhuǎn)換為objc_msgSend 的調(diào)用瞎访,兩個(gè)參數(shù)腻贰,消息接收者id和消息對(duì)應(yīng)的方法名稱SEL,同時(shí)接收消息的任何參數(shù)。
id objc_msgSend(id theReceiver, SELtheSelector, ...)
如上:
objc_msgSend(cat扒秸,@selector(eat) );
該消息做了動(dòng)態(tài)綁定的所需要的一切工作:
1播演,它首先找到 SEL 對(duì)應(yīng)的方法實(shí)現(xiàn) IMP。因?yàn)椴煌念悓?duì)同一方法可能會(huì)有不同的實(shí)現(xiàn)伴奥,所以找到的 方法實(shí)現(xiàn)依賴于消息接收者的類型写烤。2, 然后將消息接收者對(duì)象(指向消息接收者對(duì)象的指針)以及方法中指定的參數(shù)傳遞給方法實(shí)現(xiàn) IMP拾徙。
3洲炊, 最后,將方法實(shí)現(xiàn)的返回值作為該函數(shù)的返回值返回。

查找IMP的過程
我們?cè)谇懊娴念惤Y(jié)構(gòu)中也看到有一個(gè)叫 objc_cache *cache 的成員暂衡,這個(gè)緩存為提高效率而存在 的询微。每個(gè)類都有一個(gè)獨(dú)立的緩存,同時(shí)包括繼承的方法和在該類中定義的方法狂巢。撑毛。
查找過程:
當(dāng)調(diào)用[cat eat]; 這個(gè)方法的時(shí)候,編譯器會(huì)將其轉(zhuǎn)換成objc_msgSend(cat唧领,@selector(eat) );這個(gè)方法做了動(dòng)態(tài)綁定的一切工作
1藻雌,首先根據(jù)SEL去該類的方法 cache 中查找,如果找到了調(diào)到6;
2斩个,如果沒有找到胯杭,就去該類的方法列表中查找。如果在該類的方法列表中找到了受啥,跳到6做个,并將 它加入 cache 中緩存起來。根據(jù)最近使用原則腔呜,這個(gè)方法再次調(diào)用的可能性很大叁温,緩存起來可以節(jié)省下次 調(diào)用再次查找的開銷。
3核畴,如果在該類的方法列表中沒找到對(duì)應(yīng)的 IMP膝但,在通過該類結(jié)構(gòu)中的 super_class 指針在其父類結(jié)構(gòu)的方法列表中去查找,直到在某個(gè)父類的方法列表中找到對(duì)應(yīng)的 IMP谤草,返回它跟束,并加入 cache 中;
4,如果在自身以及所有父類的方法列表中都沒有找到對(duì)應(yīng)的 IMP丑孩,則看是不是可以進(jìn)行動(dòng)態(tài)方法決議
5冀宴,如果動(dòng)態(tài)方法決議沒能解決問題,進(jìn)入消息轉(zhuǎn)發(fā)流程温学。
6略贮,它首先找到 SEL 對(duì)應(yīng)的方法實(shí)現(xiàn) IMP。因?yàn)椴煌念悓?duì)同一方法可能會(huì)有不同的實(shí)現(xiàn)仗岖,所以找到的 方法實(shí)現(xiàn)依賴于消息接收者的類型逃延。7, 然后將消息接收者對(duì)象(指向消息接收者對(duì)象的指針)以及方法中指定的參數(shù)傳遞給方法實(shí)現(xiàn) IMP轧拄。
8揽祥, 最后,將方法實(shí)現(xiàn)的返回值作為該函數(shù)的返回值返回檩电。
我們可以通過 NSObject 的一些方法獲取運(yùn)行時(shí)信息或動(dòng)態(tài)執(zhí)行一些消息:
class 返回對(duì)象的類;isKindOfClass 和 isMemberOfClass 檢查對(duì)象是否在指定的類繼承體系中;
respondsToSelector 檢查對(duì)象能否指定相應(yīng)的消息;conformsToProtocol 檢查對(duì)象是否實(shí)現(xiàn)了指定協(xié)議類的方法;
methodForSelector 返回指定方法實(shí)現(xiàn)的地址拄丰。
performSelector:withObject 執(zhí)行 SEL 所指代的方法府树。

消息轉(zhuǎn)發(fā):
通常,給一個(gè)對(duì)象發(fā)送它不能處理的消息會(huì)得到出錯(cuò)提示料按,然而奄侠,Objective-C 運(yùn)行時(shí)系統(tǒng)在拋出錯(cuò)誤之前, 會(huì)給消息接收對(duì)象發(fā)送一條特別的消息 forwardInvocation 來通知該對(duì)象站绪,該消息的唯一參數(shù)是個(gè) NSInvocation 類型的對(duì)象——該對(duì)象封裝了原始的消息和消息的參數(shù)遭铺。
我們可以實(shí)現(xiàn) forwardInvocation:方法來對(duì)不能處理的消息做一些默認(rèn)的處理丽柿,也可以將消息轉(zhuǎn)發(fā)給其他對(duì) 象來處理恢准,而不拋出錯(cuò)誤。
forwardInvocation:消息給這個(gè)問題提供了一個(gè)更特別的甫题,動(dòng)態(tài)的解決方案:當(dāng)一個(gè)對(duì)象由于沒有相應(yīng)的方 法實(shí)現(xiàn)而無法響應(yīng)某消息時(shí)馁筐,運(yùn)行時(shí)系統(tǒng)將通過 forwardInvocation:消息通知該對(duì)象。每個(gè)對(duì)象都從 NSObject 類中繼承了 forwardInvocation:方法坠非。然而敏沉,NSObject 中的方法實(shí)現(xiàn)只是簡單地調(diào)用了 doesNotRecognizeSelector:。通過實(shí)現(xiàn)我們自己的 forwardInvocation:方法炎码,我們可以在該方法實(shí)現(xiàn)中將 消息轉(zhuǎn)發(fā)給其它對(duì)象盟迟。
要轉(zhuǎn)發(fā)消息給其它對(duì)象,forwardInvocation:方法所必須做的有:
1潦闲,決定將消息轉(zhuǎn)發(fā)給誰攒菠,并且 2,將消息和原來的參數(shù)一塊轉(zhuǎn)發(fā)出去歉闰。

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

轉(zhuǎn)發(fā)消息后的返回值將返回給原來的消息發(fā)送者辖众。您可以將返回任何類型的返回值,包括: id和敬,結(jié)構(gòu)體凹炸,浮 點(diǎn)數(shù)等。
forwardInvocation:方法就像一個(gè)不能識(shí)別的消息的分發(fā)中心昼弟,將這些消息轉(zhuǎn)發(fā)給不同接收對(duì)象啤它。或者它也 可以象一個(gè)運(yùn)輸站將所有的消息都發(fā)送給同一個(gè)接收對(duì)象舱痘。它可以將一個(gè)消息翻譯成另外一個(gè)消息变骡,或者 簡單的"吃掉―某些消息,因此沒有響應(yīng)也沒有錯(cuò)誤衰粹。forwardInvocation:方法也可以對(duì)不同的消息提供同樣 的響應(yīng)锣光,這一切都取決于方法的具體實(shí)現(xiàn)。該方法所提供是將不同的對(duì)象鏈接到消息鏈的能力铝耻。
注意: forwardInvocation:方法只有在消息接收對(duì)象中無法正常響應(yīng)消息時(shí)才會(huì)被調(diào)用誊爹。 所以蹬刷,如果我們 希望一個(gè)對(duì)象將 negotiate 消息轉(zhuǎn)發(fā)給其它對(duì)象,則這個(gè)對(duì)象不能有 negotiate 方法频丘,也不能在動(dòng)態(tài)方法 決議過程中為之提供實(shí)現(xiàn)办成。否則,forwardInvocation:將不可能會(huì)被調(diào)用搂漠。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末迂卢,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子桐汤,更是在濱河造成了極大的恐慌而克,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡薪伏,警方通過查閱死者的電腦和手機(jī)麻蹋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事〗钐” “怎么了?”我有些...
    開封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵冤馏,是天一觀的道長日麸。 經(jīng)常有香客問我,道長宿接,這世上最難降的妖魔是什么赘淮? 我笑而不...
    開封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮睦霎,結(jié)果婚禮上梢卸,老公的妹妹穿的比我還像新娘。我一直安慰自己副女,他們只是感情好蛤高,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著碑幅,像睡著了一般戴陡。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上沟涨,一...
    開封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天恤批,我揣著相機(jī)與錄音,去河邊找鬼裹赴。 笑死喜庞,一個(gè)胖子當(dāng)著我的面吹牛诀浪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播延都,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼雷猪,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了晰房?” 一聲冷哼從身側(cè)響起求摇,我...
    開封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎殊者,沒想到半個(gè)月后与境,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡幽污,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年嚷辅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片距误。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖扁位,靈堂內(nèi)的尸體忽然破棺而出准潭,到底是詐尸還是另有隱情,我是刑警寧澤域仇,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布刑然,位于F島的核電站,受9級(jí)特大地震影響暇务,放射性物質(zhì)發(fā)生泄漏泼掠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一垦细、第九天 我趴在偏房一處隱蔽的房頂上張望择镇。 院中可真熱鬧,春花似錦括改、人聲如沸腻豌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吝梅。三九已至,卻和暖如春惹骂,著一層夾襖步出監(jiān)牢的瞬間苏携,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來泰國打工对粪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留右冻,地道東北人穿扳。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像国旷,于是被迫代替她去往敵國和親矛物。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉跪但,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,682評(píng)論 0 9
  • 我們常常會(huì)聽說 Objective-C 是一門動(dòng)態(tài)語言履羞,那么這個(gè)「動(dòng)態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,176評(píng)論 0 7
  • 參考鏈接: http://www.cnblogs.com/ioshe/p/5489086.html 簡介 Runt...
    樂樂的簡書閱讀 2,131評(píng)論 0 9
  • 繼上Runtime梳理(四) 通過前面的學(xué)習(xí)屡久,我們了解到Objective-C的動(dòng)態(tài)特性:Objective-C不...
    小名一峰閱讀 741評(píng)論 0 3
  • 【日精進(jìn)打卡第18天】 【知~學(xué)習(xí)】 《大學(xué)》2遍共28遍 《六項(xiàng)精進(jìn)》2遍共28遍 【經(jīng)典名句分享】 齊家之道忆首,...
    光影邀青春閱讀 102評(píng)論 0 0