前言
在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