在object-c中,消息直到運行的時候才會綁定對應的實現(xiàn)蔼两,編譯器將消息表達式:
[receiver message]
轉換成對objc_msgSend消息傳遞函數的調用:
objc_msgSend(receiver, selector)//or objc_msgSend(receiver, selector, arg1, arg2, ...)
該函數包含消息的接受者祝高、函數的名稱以及對應的參數涣达。該消息傳遞函數首先去receiver類尋找selector所對應的實現(xiàn)算利,接著調用該實現(xiàn)并傳遞對應的參數册踩,最后返回函數的返回值。objc_msgSend函數由編譯器生成的效拭,自己在代碼中不能直接調用該函數暂吉。
消息傳遞的關鍵在于每個類結構都有兩個重要的元素:
- 指向父類的指針;
- 類的分發(fā)表( dispatch table)缎患,該表包含selector的名稱及對應實現(xiàn)函數的地址慕的;
消息傳遞的過程:當給一個對象發(fā)送消息的時候,消息傳遞函數首先根據對象的isa指針找到類的結構挤渔,然后在其分發(fā)表中尋找對應的selector肮街,如果找到的話就調用對應的實現(xiàn);如果找不到則會根據super class指針去其父類尋找判导,如果父類還找不到低散,會接著去父類的父類中尋找,直到NSObject類為止骡楼。
為了加速消息傳遞的過程熔号,運行時系統(tǒng)會緩存用到過的selector,每個類都有一個單獨的cache鸟整,它可以緩存繼承或自己定義的方法引镊。在根據selector名字搜索分發(fā)表之前,消息路由會首先檢查receiver類的cache是否已經緩存對應的selector篮条,如果有的話就直接調用對應的實現(xiàn)弟头。
獲取函數地址
繞過消息綁定的唯一方法就是獲取函數的地址,然后直接調用涉茧。這在極少的情況下適用:當一個方法連續(xù)執(zhí)行很多次赴恨,而你想避免每次消息傳遞帶來的開銷。
通過methodForSelector:方法你可以獲取selector對應實現(xiàn)的指針伴栓,該指針必須轉換成合適的函數類型:
void (*setter)(id, SEL, BOOL);//設置對應的函數指針類型
int i;
setter = (void (*)(id, SEL, BOOL))[target
methodForSelector:@selector(setFilled:)];
for ( i = 0 ; i < 1000 ; i++ )
setter(targetList[i], @selector(setFilled:), YES);//通過函數指針調用對應實現(xiàn)
使用methodForSelector:繞開動態(tài)綁定節(jié)約了消息傳遞時時間伦连,然后這有在一個特定的方法重復調用多次的時候才能體現(xiàn)出來,比如上述的例子钳垮。
消息動態(tài)解析
在某些情況下你可能需要動態(tài)的提供方法惑淳,比如在oc中你聲明某個屬性為@dynamic類型,編譯器就認為與這屬性相關的方法會動態(tài)提供饺窿。你可以通過實現(xiàn)resolveInstanceMethod:和 resolveClassMethod:動態(tài)的為selector提供實現(xiàn)方法歧焦,objective-c方法本質上就是一個帶有至少兩個參數(_self和_cmd)的c函數,你可以通過 class_addMethod為類添加一個函數肚医,參見下面的例子:
@interface SomeClass : NSObject
@property (assign, nonatomic) float objectTag;
@end
@implementation SomeClass
@dynamic objectTag; //聲明為dynamic
//添加setter實現(xiàn)
void dynamicSetMethod(id self,SEL _cmd,float w){
printf("dynamicMethod-%s\n",[NSStringFromSelector(_cmd)
cStringUsingEncoding:NSUTF8StringEncoding]);
printf("%f\n",w);
objc_setAssociatedObject(self, ObjectTagKey, [NSNumber numberWithFloat:w], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
//添加getter實現(xiàn)
void dynamicGetMethod(id self,SEL _cmd){
printf("dynamicMethod-%s\n",[NSStringFromSelector(_cmd)
cStringUsingEncoding:NSUTF8StringEncoding]);
[objc_getAssociatedObject(self, ObjectTagKey) floatValue];
}
//解析selector方法
+(BOOL) resolveInstanceMethod: (SEL) sel{
NSString *methodName=NSStringFromSelector(sel);
BOOL result=NO;
//動態(tài)的添加setter和getter方法
if ([methodName isEqualToString:@"setObjectTag:"]) {
class_addMethod([self class], sel, (IMP) dynamicSetMethod,
"v@:f");
result=YES;
}else if([methodName isEqualToString:@"objectTag"]){
class_addMethod([self class], sel, (IMP) dynamicGetMethod,
"v@:f");
result=YES;
}
return result;
}
看看class_addMethod函數:
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
參數說明:
cls:添加方法的類
name:selector名稱
imp:selector對應得具體實現(xiàn)
types:一個定義該函數返回值類型和參數類型的字符串
通過上面動態(tài)的添加getter和setter方法后绢馍,調用如下代碼程序就不會crash向瓷。
self.objectTag=10.0f;
float tag = self.objectTag
在消息轉發(fā)機制開始之前,一個類有機會先動態(tài)解析該方法舰涌,如果你實現(xiàn)了 resolveInstanceMethod:猖任,但是對一些特定的selectors想啟用消息轉發(fā)機制,只需要過濾這些selector返回NO即可舵稠。