object-c語言的動態(tài)性
Objective-C 是一門極其動態(tài)的語言,許多東西都可以推遲到運行時決定寂玲、修改殷绍。那么到底何為動態(tài)、何為靜態(tài)桐经?我們通過一個簡單的例子對比下
/*********** 例1 靜態(tài)綁定 ***********/
#import <stdio.h>
void printHello() {
printf("Hello, world!\n");
}
void printGoodbye() {
printf("Goodbye, world!\n");
}
void saySomething(int type)
{
if (type == 0) {
printHello();
} else {
printGoodbye();
}
return 0;
}
/*********** 例2 動態(tài)綁定 ***********/
#import <stdio.h>
void printHello() {
printf("Hello, world!\n");
}
void printGoodbye() {
printf("Goodbye, world!\n");
}
void saySomething(int type)
{
void (*func)();
if (type == 0) {
func = printHello;
} else {
func = printGoodbye;
}
func();
return 0;
}
例1的代碼在編譯期毁兆,編譯器就已經(jīng)知道了有 void printHello()、void printGoodbye() 倆函數(shù)阴挣,并且在 saySomething() 函數(shù)中气堕,調(diào)用的函數(shù)明確,可以直接將函數(shù)名硬編碼成地址畔咧,生成調(diào)用指令茎芭,這就是 靜態(tài)綁定(static binding)。那么例2呢誓沸?例2的調(diào)用的是 func() 函數(shù)梅桩,而這函數(shù)實際調(diào)用的地址只能到程序運行時才能確定,這就是所謂的 動態(tài)綁定(dynamic binding)拜隧。動態(tài)綁定將函數(shù)調(diào)用從編譯期推遲到了運行時宿百。
在 Objective-C 中趁仙,向?qū)ο髠鬟f消息,就會使用這種動態(tài)綁定機(jī)制來決定需要調(diào)用的方法垦页,這種動態(tài)特性使得 Objective-C 成為一門真正動態(tài)的語言雀费。
objec_msgSend 函數(shù)
Objective-C 的方法調(diào)用通常都是下面這種形式
id returnValue = [someObject messageName:parameter];
這種方法調(diào)用其實就是消息傳遞,編譯器看到這條消息會轉(zhuǎn)換成一條標(biāo)準(zhǔn)的 C 語言函數(shù)調(diào)用
id returnValue = objc_msgSend(someObject,
@selector(messageName:),
parameter);
用消息傳遞的話來解釋就是:向 someObject
對象發(fā)送了一個名叫 messageName
的消息痊焊,這個消息攜帶了一個叫 parameter
的參數(shù)坐儿。這里用到了一個 objc_msgSend
函數(shù),其函數(shù)原型如下
void objc_msgSend(id self, SEL cmd, ...);
這是一個可變參數(shù)的函數(shù)宋光,第一個參數(shù)代表消息接收者貌矿,第二個代表 SEL 類型,后面的參數(shù)就是消息傳遞中使用的參數(shù)罪佳。
那么什么是 SEL 呢逛漫?SEL 就是代碼在編譯時,編譯器根據(jù)方法簽名來生成的一個唯一 ID赘艳。此 ID 可以用以區(qū)分不同的方法酌毡,只要 ID 一致,即看成同一個方法蕾管,ID 不同枷踏,即為不同的方法。
當(dāng)進(jìn)行消息傳遞掰曾,對象在響應(yīng)消息時旭蠕,是通過 SEL 在 methodlist 中查找函數(shù)指針 IMP,找到后直接通過指針調(diào)用函數(shù)旷坦,這其實就是前文介紹的 動態(tài)綁定掏熬。若是找到對應(yīng)函數(shù)就跳轉(zhuǎn)到實現(xiàn)代碼,若找不到秒梅,就沿著繼承鏈往上查找旗芬,直到找到相應(yīng)的實現(xiàn)代碼為止。若最終還是沒找到實現(xiàn)代碼捆蜀,說明當(dāng)前對象無法響應(yīng)此消息疮丛,接下來就會執(zhí)行 消息轉(zhuǎn)發(fā) 操作,以試圖找到一個能響應(yīng)此消息的對象辆它。
// 獲取 SEL
SEL sel = @selector(methodName);
// 獲取 IMP
IMP imp = methodForSelector(sel);
消息轉(zhuǎn)發(fā)
消息轉(zhuǎn)發(fā)并不神奇誊薄,我們其實早已接觸過,只是不知道而已
-[__NSCFNumber lowercaseString]:unrecognized selector sent to instance 0x87
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason:'-[NSCFNumber lowercaseString]:unrecognized selector sent to instance 0x87'
這段異常代碼就是由 NSObject 的 doesNotRecognizeSelector: 方法所拋出的娩井,異常表明:消息的接收者類型為 __NSCFNumber暇屋,無法響應(yīng) lowercaseString 消息,從而轉(zhuǎn)發(fā)給 NSObject 處理洞辣。
消息轉(zhuǎn)發(fā)分為三大階段
- 第一階段先征詢消息接收者所屬的類咐刨,看其是否能動態(tài)添加方法昙衅,以處理當(dāng)前這個無法響應(yīng)的 selector,這叫做 動態(tài)方法解析(dynamic method resolution)定鸟。如果運行期系統(tǒng)(runtime system) 第一階段執(zhí)行結(jié)束而涉,接收者就無法再以動態(tài)新增方法的手段來響應(yīng)消息,進(jìn)入第二階段联予。
- 第二階段看看有沒有其他對象(備援接收者啼县,replacement receiver)能處理此消息。如果有沸久,運行期系統(tǒng)會把消息轉(zhuǎn)發(fā)給那個對象季眷,轉(zhuǎn)發(fā)過程結(jié)束;如果沒有卷胯,則啟動完整的消息轉(zhuǎn)發(fā)機(jī)制子刮。
- 第三階段 完整的消息轉(zhuǎn)發(fā)機(jī)制。運行期系統(tǒng)會把與消息有關(guān)的全部細(xì)節(jié)都封裝到 NSInvocation 對象中窑睁,再給接收者最后一次機(jī)會挺峡,令其設(shè)法解決當(dāng)前還未處理的消息。
動態(tài)方法解析
對象在收到無法響應(yīng)的消息后担钮,會調(diào)用其所屬類的下列方法
/**
* 如果尚未實現(xiàn)的方法是實例方法橱赠,則調(diào)用此函數(shù)
*
* @param selector 未處理的方法
*
* @return 返回布爾值,表示是否能新增實例方法用以處理selector
*/
+ (BOOL)resolveInstanceMethod:(SEL)selector;
/**
* 如果尚未實現(xiàn)的方法是類方法箫津,則調(diào)用此函數(shù)
*
* @param selector 未處理的方法
*
* @return 返回布爾值狭姨,表示是否能新增類方法用以處理selector
*/
+ (BOOL)resolveClassMethod:(SEL)selector;
方法返回布爾類型,表示是否能新增一個方法來處理 selector鲤嫡,此方案通常用來實現(xiàn) @dynamic 屬性送挑。
// 動態(tài)方法解析,先進(jìn)行這一步
+ (BOOL)resolveInstanceMethod:(SEL)name
// 實例方法
{
NSLog(@" >> Instance resolving %@", NSStringFromSelector(name));
// MissMethod為調(diào)用的方法名
if (name == @selector(dynamicParserMethod)) {
class_addMethod([self class], name, (IMP)dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:name];
}
// 處理該類無法處理消息的方法
void dynamicMethodIMP(id self, SEL _cmd) {
NSLog(@" >> dynamicMethodIMP");
}
備援接收者
如果無法 動態(tài)解析方法暖眼,運行期系統(tǒng)就會詢問是否能將消息轉(zhuǎn)給其他接收者來處理,對應(yīng)的方法為
// 備援接收者的處理
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@" >> forwardingTargetForSelector %@", NSStringFromSelector(aSelector));
if (aSelector == @selector(testReceiveObject)) {
// 返回接受該方法的處理類
ReceiveObject *receiveObject = [[ReceiveObject alloc] init];
return receiveObject;
}
return [super forwardingTargetForSelector:aSelector];
}
在對象內(nèi)部纺裁,可能還有其他對象诫肠,該對象可通過此方法將能夠處理 selector 的相關(guān)內(nèi)部對象返回,在外界看來欺缘,就好像是該對象自己處理的似得栋豫。
完整的消息轉(zhuǎn)發(fā)機(jī)制
如果前面兩步都無法處理消息,就會啟動完整的消息轉(zhuǎn)發(fā)機(jī)制谚殊。首先創(chuàng)建 NSInvocation 對象丧鸯,把尚未處理的那條消息有關(guān)的全部細(xì)節(jié)裝在里面,在觸發(fā) NSInvocation 對象時嫩絮,消息派發(fā)系統(tǒng)(message-dispatch system)將會把消息指派給目標(biāo)對象丛肢。對應(yīng)的方法為
/**
* 消息派發(fā)系統(tǒng)通過此方法围肥,將消息派發(fā)給目標(biāo)對象,實現(xiàn)消息轉(zhuǎn)發(fā)
*
* @param anInvocation 之前創(chuàng)建的NSInvocation實例對象,用于裝載有關(guān)消息的所有內(nèi)容
*/
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"forwardInvocation = %@", NSStringFromSelector(anInvocation.selector));
if (anInvocation.selector == @selector(forwardInvocationMsg)) {
ReceiveObject *receiveObject = [[ReceiveObject alloc] init];
if ([receiveObject respondsToSelector:[anInvocation selector]]) {
[anInvocation invokeWithTarget:receiveObject];
}else {
[super forwardInvocation:anInvocation];
}
}
}
// 調(diào)用forwardInvocation之前要先進(jìn)行對方法進(jìn)行注冊
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
NSMethodSignature* signature = [super methodSignatureForSelector:selector];
if (!signature) {
// 注冊
ReceiveObject *receiveObject = [[ReceiveObject alloc] init];
signature = [receiveObject methodSignatureForSelector:selector];
}
return signature;
}
這個方法可以實現(xiàn)的很簡單蜂怎,通過改變調(diào)用的目標(biāo)對象穆刻,使得消息在新目標(biāo)對象上得以調(diào)用即可。然而這樣實現(xiàn)的效果與 備援接收者 差不多杠步,所以很少人會這么做氢伟。更加有用的實現(xiàn)方式為:在觸發(fā)消息前,先以某種方式改變消息內(nèi)容幽歼,比如追加另一個參數(shù)朵锣、修改 selector 等等。