Objective-C 又提供了IMP 類型,IMP 表示指向實現方法的指針(函數指針),經過它,你可
以直接訪問一個實現方法,從而避免了[xxx message]的靜態調用方式,須要首先經過SEL 確
定方法,而後再經過IMP 找到具體的實現方法,最後再發送消息所帶來的執行效率問題。
通常,若是你在屢次循環中反覆調用一個方法,用IMP 的方式,會比直接向對象發送消息
高效一些。
例:html
Person.m: #import "Person.h" @implementation Person @synthesize name; @synthesize weight; -(Person*) initWithWeight: (int) w { self=[super init]; if (self) { weight=w; } return self; } -(void) print: (NSString*) str { NSLog(@"%@ %@",str,name); } -(void) dealloc{ [self setName:nil]; [super dealloc]; } @end main.m: int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Person *person=[[Person alloc] initWithWeight:68]; person.name=@"Jetta"; SEL print_sel=NSSelectorFromString(@"print:"); IMP imp=[person methodForSelector: print_sel]; imp(person,print_sel,@"*********"); [pool drain]; return 0; }
這裏咱們看到要得到IMP 的指針,能夠經過NSObject 中的methodForSelector: (SEL)方法,訪
問這個指針函數,咱們使用imp(id,SEL,argument1,… …),第一個參數是調用方法的對象,第
二個方法是方法的選擇器對象,第三個參數是可變參數,表示傳遞方法須要的參數。
(3.)objc_msgSend函數:
經過isa 指針的講解,咱們知道Objective-C 中的方法調用是在運行時纔去綁定的,再進一步
看,編譯器會把對象消息發送[xxx method]轉換爲objc_msgSend(id receiver,SEL selector,參數…)
的函數調用。所以上面例子中的print 方法你也能夠像下面這樣調用:
objc_msgSend(person,print_sel,@"++++++++");
固然,這是編譯器要作的事情,你在寫代碼的時候,是不須要直接使用這種寫法的。
綜合isa、SEL、IMP 的講解,實際上objc_msgSend 的調用過程就應該是這樣的。
A.首先經過第一個參數的receiver,找到它的isa 指針,而後在isa 指向的Class 對象中使用
第二個參數selector 查找方法;
B.若是沒有找到,就使用當前Class 對象中的新的isa 指針到上一級的父類的Class 對象中查
找;
C.當找到方法後,再依據receiver 的中的self 指針找到當前的對象,調用當前對象的具體實
現的方法(IMP 指針函數),而後傳遞參數,調用實現方法。
D.假如一直找到NSObject 的Class 對象,也沒有找到你調用的方法,就會報告不能識別發送
消息的錯誤。
(4.)動態方法解析:
咱們在Objective-C 2.0 的新特性中的屬性訪問器一節中,實際忽略了一個內容,那就是動態
屬性。Objective-C 2.0 中增長了@dynamic 指令,表示變量對應的屬性訪問器方法,是動態實
現的,你須要在NSObject 中繼承而來的+(BOOL) resolveInstanceMethod:(SEL) sel 方法中指定
動態實現的方法或者函數。
例:
ios
Person.h: @interface Person : NSObject{ NSString *name; float weight; } -(Person*) initWithWeight: (int) weight; @property (retain,readwrite) NSString* name; @property (readonly)float weight; @property float height; -(void) print: (NSString*) str; @end Person.m: void dynamicMethod(id self,SEL _cmd,float w){ printf("dynamicMethod-%s\n",[NSStringFromSelector(_cmd) cStringUsingEncoding:NSUTF8StringEncoding]); printf("%f\n",w); } @implementation Person @synthesize name; @synthesize weight; @dynamic height; -(Person*) initWithWeight: (int) w{ self=[super init]; if (self) { weight=w; } return self; } -(void) print: (NSString*) str{ NSLog(@"%@%@",str,name); } +(BOOL) resolveInstanceMethod: (SEL) sel{ NSString *methodName=NSStringFromSelector(sel); BOOL result=NO; //看看是否是咱們要動態實現的方法名稱 if ([methodName isEqualToString:@"setHeight:"]) { class_addMethod([self class], sel, (IMP) dynamicMethod, "v@:f"); result=YES; } return result; } -(void) dealloc{ [self setName:nil]; [super dealloc]; } @end
這裏咱們對於接口中的height在實現類中使用了@dynamic指令,緊接着,你須要指定一個函
數或者其餘類的方法做爲height的setter、getter方法的運行時實現。爲了簡單,咱們指定
了Person.m中定義的函數(注意這是C語言的函數,不是Objective-C的方法)dynamicMethod
做爲height的setter方法的運行時實現。被指定爲動態實現的方法的dynamicMethod的參數
有以下的要求:
A.第一個、第二個參數必須是id、SEL;
B.第三個參數開始,你能夠按照原方法(例如:setHeight:(float))的參數定義。
再接下來,你須要覆蓋NSObject 的類方法resolveInstanceMethod,這個方法會把須要動態
實現的方法(setHeight:)的選擇器傳遞進來,咱們判斷一下是不是須要動態實現的選擇器,
若是是就把處理權轉交給dynamicMethod。如何轉交呢?這裏咱們就要用到運行時函數
class_addMethod(Class,SEL,IMP,char[])。
運行時函數位於objc/runtime.h,正如名字同樣,這裏面都是C 語言的函數。按照這些函數
的功能的不一樣,主要分爲以下幾類:操做類型、操做對象、操做協議等。大多數的函數均可
以經過名字看出是什麼意思,例如:class_addProtocol 動態的爲一個類型在運行時增長協議、
objc_getProtocol 把一個字符串轉換爲協議等。具體這些運行時函數都是作什麼用的,你可
以參看Apple 官方頁面:
http://developer.apple.com/library/ios/documentation/Cocoa/Reference/ObjCRuntimeRef/Refer
ence/reference.html#//apple_ref/doc/uid/TP40001418
言歸正傳,咱們來解釋一下這裏須要用到的class_addmethod 方法,這個方法有四個參數,
Class 表示你要爲哪一個類型增長方法,SEL 參數表示你要增長的方法的選擇器,IMP 表示你要
添加的方法的運行時的具體實現的函數指針。其實在這裏你可以看出SEL 並不能在運行時找
到真正要調用的方法,IMP 才能夠真正的找到實現方法的。
在講解第四個參數char[]以前,咱們先看一下第一篇文檔中提到的@encode 指令,在把任意
非Objective-C 對象類型封裝爲NSValue 類型的時候使用到了@encode 指令,但當時咱們沒
有詳細說明這個指令的含義。實際上@encode()能夠接受任何類型,Objective-C 中用這個指
令作類型編碼,它能夠把任何一個類型轉換爲字符串,譬如:void 類型被編碼以後爲v,對
象類型爲@,SEL 類型爲:等,具體的你能夠參看Apple 官方頁面關於Type Encoding 的描述:
http://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/A
rticles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100-SW
如今咱們來正式的看如下第四個參數v@:f 的含義,它描述了IMP 指向的函數的描述信息,
按照@encode 指令編譯以後的字符說明,第一個字符v 表示返回值爲void,剩餘的字符爲
dynamicMethod 函數的參數描述,@表示第一個參數id,:天然就是第二個參數SEL,f 就是
第三個參數float。因爲前面說過動態方法的實現的前兩個參數必須是id、SEL,因此第四個
參數中的字符串的第2、三個字符必定是@:。
咱們看到resolveInstanceMethod 方法的返回值爲BOOL,也就是這個方法返回YES 表示找到
了動態方法的具體實現,不然就表示沒有在運行時找到真實的實現,程序就彙報錯。
通過了上面的處理,Objective-C 的運行時只要發現你調用了@dynamic 標註的屬性的setter、
getter 方法,就會自動到resolveInstanceMethod 裏去尋找真實的實現。這也就是說你在
main.m 中調用peson.height 的時候,實際上dynamicMethod 函數被調用了。
實際上除了@dynamic 標註的屬性以外,若是你調用了類型中不存在的方法,也會被
resolveInstanceMethod 或者resolveClassMethod 截獲,但因爲你沒有處理,因此會報告不能
識別的消息的錯誤。
你可能在感嘆一個@dynamic 指令用起來真是麻煩,我也是研究了半天Apple 官方的晦澀的
鳥語才搞明白的。不過好在通常Objective-C 的運行時編程用到的並很少,除非你想設計一
個動態化的功能,譬如:從網絡下載一個升級包,不須要退出原有的程序,就能夠動態的替
換掉舊的功能等相似的需求。
(5.)消息轉發:
在前面的objc_msgSend()函數的最後,咱們總結了Objective-C 的方法調用過程,在最後一步
咱們說若是一路找下來仍是沒有找到調用的方法,就會報告錯誤,實際上這裏有個細節,那
就是最終找不到調用的方法的時候,系統會調用-(void) forwardInvocation: (NSInvocation*)
invocation 方法,若是你的對象沒有實現這個方法,就調用NSObject 的forwardInvocation 方
法,那句不能識別消息的錯誤,實際就是NSObject 的forwardInvocation 拋出來的異常。
咱們這裏告訴你這個系統內部的實現過程,實際是要告訴你,你能夠覆蓋forwardInvocation
方法,來改變NSObject 的拋異常的處理方式。譬如:你能夠把A 不能處理的消息轉發給B
去處理。
NSInvocation 是一個包含了receiver、selector 的對象,也就是它包含了向一個對象發送消息
的全部元素:對象、方法名、參數序列,你能夠調用NSInvocation 的invoke 方法將這個消息
激活。
例:
編程
main.m: int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Person *person=[[Person alloc] init]; person.name=@"Jetta"; [person fly]; [person release]; [pool drain]; return 0; }
這裏咱們調用了一個Person 中不存在的方法fly。
Bird.m:
#import "Bird.h"
@implementation Bird
-(void) fly{
printf("Bird Can fly!");
}
@end
Person.m
@implementation Person
@synthesize name;
@synthesize weight;
-(NSMethodSignature*) methodSignatureForSelector:(SEL)selector{
//首先調用父類的方法
NSMethodSignature *signature=
[super methodSignatureForSelector: selector];
//若是當前對象沒法迴應此selector,那麼selector構造的方法簽名必然爲nil
if (!signature) {
//首先判斷Bird的實例是否有能力迴應此selector
if ([Bird instancesRespondToSelector:selector]) {
//獲取Bird的selector的方法簽名對象
signature=[Bird instanceMethodSignatureForSelector:
selector];
}
}
return signature;
}
-(void) forwardInvocation: (NSInvocation*) invocation{
//首先驗證Bird是否有能力迴應invocation中包含的selector
if ([Bird instancesRespondToSelector:[invocation selector]]) {
//建立要移交消息響應權的實例bird
Bird *bird=[Bird new];
//激活invocation中的消息,可是消息的響應者是bird,而不是默認的self。
[invocation invokeWithTarget:bird];
}
}
-(void) dealloc{
[self setName:nil];
[super dealloc];
}
@end
下面咱們來詳細分析一下若是你想把不能處理的消息轉發給其餘的對象,須要通過哪一個幾個
步驟:
A.首先,你要覆蓋NSObject中的methodSignatureForSelector方法。這是由於你若是想把消
息fly從Person轉發給Bird處理,那麼你必須將NSInvocation中包含的Person的fly的方法籤
名轉換爲Bird的fly的方法簽名,也就是把方法簽名糾正一下。
由此,你也看出來NSInvocation的建立,內部使用了兩個對象,一個是receiver,一個是
NSMethodSignature,而NSMethodSignature是由SEL建立的。NSInvocation確實存在一個類方
法invocationWithMethodSignature返回自身的實例。
B.而後咱們覆蓋forwardInvocation方法,使用的不是invoke方法,而是invokeWithTarget方法,
也就是把調用權由self轉交給bird。
實際上消息轉發機制不只能夠用來處理找不到方法的錯誤,你還能夠變相的實現多繼承。假
如咱們的Person 想要擁有Bird、Fish 的全部功能,其實你能夠盡情的用Person 的實例調用
Bird、Fish 的方法,只要在Person 的forwardInvocation 裏,把消息的響應權轉交給Bird 或者
Fish 的實例就能夠了。不過這種作法實在有點兒BT,除非萬不得已,不然千萬不要這麼作,
可是你也從這裏可以看出來Objective-C 這種語言有多麼的靈活、強大,這是JAVA 所徹底不
能相比的。
網絡