Objective-CiOS之runtimeiOS模块详解

ios runtime浅析(-):消息传递与动态解析

2015-05-25  本文已影响3946人  树下老男孩

在object-c中,消息直到运行的时候才会绑定对应的实现,编译器将消息表达式:

[receiver message] 

转换成对objc_msgSend消息传递函数的调用:

objc_msgSend(receiver, selector)//or objc_msgSend(receiver, selector, arg1, arg2, ...)

该函数包含消息的接受者、函数的名称以及对应的参数。该消息传递函数首先去receiver类寻找selector所对应的实现,接着调用该实现并传递对应的参数,最后返回函数的返回值。objc_msgSend函数由编译器生成的,自己在代码中不能直接调用该函数。

消息传递的关键在于每个类结构都有两个重要的元素:

消息传递的过程:当给一个对象发送消息的时候,消息传递函数首先根据对象的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即可。

上一篇下一篇

猜你喜欢

热点阅读