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) 的方法鏈表。如下圖
方法的含義:
這里所說的方法鏈表里面儲(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)用搂漠。