runtime基础篇

runtime是什么

对于刚接触iOS或者初学者,runtime有点像十万个为什么,处处有疑惑。但作为一个iOS开发人员,runtime是深刻掌握Objective C所必须掌握的东西。它应该是OC最核心的部分,只有掌握好它,你才能理解其底层的原理和OC是个动态语言。要练成盖世神功,需练就自身深厚的内功,而runtime就是iOS开发的上层内功。

runtime是一个c和汇编写的动态库,他就像一个小小的系统,将OC和C紧密联系,它主要做两件事:

1、封装C语言的结构体和函数,让开发者在运行时创建、检查或者修改类、对象和方法等等。

2、传递消息,找出最终的执行代码。

为了让大家更好的理解runtime,举个例子 ---- 比如:给对象发送消息,如下面代码所示:

id returnValue = [someObject messageName:parameter];
在本例中,someObject叫做“接收者”(receiver),messageName叫做“选择子”(selector)。选择子与参数合起来称为“消息”(message)。编译器看到此消息后,将其转换为一条标准的C语言函数调用,所调用的函数是消息传递机制中的核心函数,叫做objc_msgSend,其原型如下:

void objc_msgsend(id self,SEL cmd,...)
这是个“参数个数可变的函数”,能接受两个或者两个以上的参数。第一个参数代表接收者,第二个参数代表选择子,后续参数就是消息中的那些参数,其顺序不变。
除了上述提到的发送消息,runtime还提供了很多类似于它的方法,比如:

(BOOL)resolveInstanceMethod:(SEL)selector;  //消息转发
objc_property_t * class_copyPropertyList(Class cls,unsigned int *ouCount); //获取属性列表
BOOL class_addMethod(class cls,SEL name,IMP imp,const char *types); //添加方法
void method_exchangeImplementations(Method m1,Method m2); //交换方法
runtime这么好,我们能利用它做什么事情呢?

1.遍历对象的属性  (JSON转模型)

2.动态添加方法,修改属性,替换方法 (swizzling  参考: http://blog.csdn.net/jasonjwl/article/details/50696424)

3.方法拦截调用  (给someObject发送messageName消息,但someObject不知道怎么messageName,那我们可以拦截,给该方法动态添加一个实现)
消息发送流程

以上面的例子进行说明  

id returnValue = [someObject messageName:parameter];

1.编译器会把id returnValue=[someObject messageName:parameter]转化为id returnValue = objc_msgSend(someOject,@selector(messageName:),parameter);

2.objc_msgSend会把匹配结果缓存在“快速映射表”里面,每个类都有这样一块缓存,若是稍后还向该类发送与选择相同的消息,执行起来会很快。

3.若是能找到与选择子名称相符的方法,就跳至其实现代码。

4.若是找不到,那就沿着继承体系继续向上查找,等找到合适的方法之后在跳转。

5.如果最终还是找不到相符的方法,那就执行“消息转发”操作。

消息转发流程

1、动态方法解析
对象在收到无法解读(假设someObject的messageName方法尚未实现)的消息后,首先将调用其所属类的下列类方法:

+(BOOL)resolveClassMethod:(SEL)sel
该方法的参数就是那个未知的选择子,其返回值为Boolean类型,表示这个类是否能新增一个实例方法用以处理此选择子。在继续往下执行转发机制之前,本类有机会新增一个处理此选择子的方法。假如尚未实现的方法不是实例方法而是类方法,那么运行期系统就会调用另外一个方法,该方法与“resolveInstanceMethod:”类似,叫做"resolveClassMethod"。

使用这种方法的前提是:相关方法的实现代码已经写好,只等着运行的时候动态插在类里面就可以了。此方案常用实现@dynamic属性,比如说,要访问CoreData框架中NSManagedObjects对象的属性时就可以这么做,因为实现这些属性所需的存取方法在编译期就能确定。

id autoDictionaryGetter(id self,SEL _cmd);
void autoDictionarySetter(id self,SEL _cmd,id value);

+(BOOL)resolveClassMethod:(SEL)selector{
    NSString *selectorString = NSStringFromSelector(selector);
    if (/* selector is from a @dynamic property */) {
        if ([selectorString hasPrefix:@"set"]) {
            class_addMethod(self,selector,(IMP)autoDictionarySetter,"v@:@");
        }
        else{
            class_addMethod(self,selector,(IMP)autoDictionaryGetter,"@@:");
        }
        return YES;
    }
    return [super resolveInstanceMethod:selector]

}

首先将选择子化为字符串,然后检测其是否表示设置方法。若前缀为set,则表示设置方法,否则就是获取方法。

2、备援接受者

  当前接受者还有第二次机会能处理位置的选择子,在这一步中,运行期系统会问它:能不能把这条消息转给其他接收者来处理。处理方法如下:

-(id)forwardingTargetForSelector:(SEL)aSelector
方法参数代表上文提到的未知的选择子(messageName),若当前接收者能找到备援对象,则将其返回,若找不到,就返回nil。通过此方案,我们可以用”组合“来模拟出“多重继承”的某些特性。在一个对象内部,可能还有一系列其他对象,该对象可经由此方法将能够处理某选择子的相关内部对象返回,这样的话,在外界看来,好像是该对象亲自出来了这些消息似的。请注意,我们无法操作经由这一步所转发的消息。若是想在发送给备援接收者之前先修改消息内容,那就得通过完整的消息转发机制来做了。

3、完整的消息转发

如果转发算法已经来到这一步的话,那么唯一能做的就是启用完整的消息转发机制了。首先创建NSInvocation对象,把与尚未处理的那条消息有关的全部细节都封于其中。

此对象包含选择子、目标与参数。在触发NSInvocation对象时,“消息派发系统”将亲自出马,把消息指派到目标对象。此步骤会调用下列方法来转发消息:

-(void)forwardInvocation:(NSInvocation *)anInvocation
这个方法可以实现得很简单:只需改变调用目标,使消息在新目标上得以调用即可。然而这样实现出来的方法与“备援接收者”方案所实现的方法等效,所以很少有人采用这么简单的实现方式。比较有用的实现方式为:在触发消息前,先以某种方式改变消息内容,比如追加另外一个参数,或是改换选择子等等。

实现此方法时,若发现某调用操作不应由本类处理,则需调用超类的同名方法。这样的话,继承体系中的每个类都有机会处理此调用请求,直至NSObject。如果最后调用了NSOject类的方法,俺么该方法还会继而调用“doesNotRecognizeSelector:”以抛出异常,此异常表明选择子最终未能得到处理。

消息转发全流程

图1这张流程图描述了消息转发机制处理消息的各个步骤

图1 消息转发

接收者在每一步中均有机会处理消息。步骤越往后,处理消息的代价就越大。最好能在第一步就处理完,这样的话,运动期系统就可以将此方法缓存起来。


参考文献

《Effective Objective-c 2.0》编写高质量iOS与OS X代码的52个有效方法



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值