ios runtime浅析(-):消息传递与动态解析
在object-c中,消息直到运行的时候才会绑定对应的实现,编译器将消息表达式:
[receiver message]
转换成对objc_msgSend消息传递函数的调用:
objc_msgSend(receiver, selector)//or objc_msgSend(receiver, selector, arg1, arg2, ...)
该函数包含消息的接受者、函数的名称以及对应的参数。该消息传递函数首先去receiver类寻找selector所对应的实现,接着调用该实现并传递对应的参数,最后返回函数的返回值。objc_msgSend函数由编译器生成的,自己在代码中不能直接调用该函数。
消息传递的关键在于每个类结构都有两个重要的元素:
- 指向父类的指针;
- 类的分发表( dispatch table),该表包含selector的名称及对应实现函数的地址;
消息传递的过程:当给一个对象发送消息的时候,消息传递函数首先根据对象的isa指针找到类的结构,然后在其分发表中寻找对应的selector,如果找到的话就调用对应的实现;如果找不到则会根据super class指针去其父类寻找,如果父类还找不到,会接着去父类的父类中寻找,直到NSObject类为止。
为了加速消息传递的过程,运行时系统会缓存用到过的selector,每个类都有一个单独的cache,它可以缓存继承或自己定义的方法。在根据selector名字搜索分发表之前,消息路由会首先检查receiver类的cache是否已经缓存对应的selector,如果有的话就直接调用对应的实现。
获取函数地址
绕过消息绑定的唯一方法就是获取函数的地址,然后直接调用。这在极少的情况下适用:当一个方法连续执行很多次,而你想避免每次消息传递带来的开销。
通过methodForSelector:方法你可以获取selector对应实现的指针,该指针必须转换成合适的函数类型:
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);//通过函数指针调用对应实现
使用methodForSelector:绕开动态绑定节约了消息传递时时间,然后这有在一个特定的方法重复调用多次的时候才能体现出来,比如上述的例子。
消息动态解析
在某些情况下你可能需要动态的提供方法,比如在oc中你声明某个属性为@dynamic类型,编译器就认为与这属性相关的方法会动态提供。你可以通过实现resolveInstanceMethod:和 resolveClassMethod:动态的为selector提供实现方法,objective-c方法本质上就是一个带有至少两个参数(_self和_cmd)的c函数,你可以通过 class_addMethod为类添加一个函数,参见下面的例子:
@interface SomeClass : NSObject
@property (assign, nonatomic) float objectTag;
@end
@implementation SomeClass
@dynamic objectTag; //声明为dynamic
//添加setter实现
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实现
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;
//动态的添加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对应得具体实现
types:一个定义该函数返回值类型和参数类型的字符串
通过上面动态的添加getter和setter方法后,调用如下代码程序就不会crash。
self.objectTag=10.0f;
float tag = self.objectTag
在消息转发机制开始之前,一个类有机会先动态解析该方法,如果你实现了 resolveInstanceMethod:,但是对一些特定的selectors想启用消息转发机制,只需要过滤这些selector返回NO即可。