Objective-C Runtime 解读 (二)

Objective-C Runtime 解读 (二)

关于runtime的解读, 前一章节主要讲解了基本的概念, 其实runtime的运用是无处不在的, 本章节主要解读runtime在”消息转发机制”中的体现.

消息发送的理解

其实, 我们一般所说的函数调用, 在OC中我们更习惯叫做消息发送, 一般会这样写: [someObj dosomething], 这种[ ]的写法, 其实就是消息发送, 其实最终是转换成了下面的形式:

objc_msgSend(someObj,@selector(dosomething));

有参数的是这样:

objc_msgSend(someObj,@selector(dosomething:),var1);

看到这里, 其实我们就已经很清楚, 为什么说OC是基于消息发送的机制, 其实底层也就是调用了C函数.

消息发送机制

可能我们经常遇到下面的崩溃信息:

2016-04-01 11:41:03.867 RunTimeTestDemo[19405:1760968] -[ViewController clickAction:]: unrecognized selector sent to instance 0x7fff33c27600
2016-04-01 11:41:03.874 RunTimeTestDemo[19405:1760968] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[ViewController clickAction:]: unrecognized selector sent to instance 0x7fff33c27600'

出现这种情况就是, 我们发送的消息没有被正确解析出来, 那么, 接下来就详细介绍OC中的动态消息转发机制.

其实消息的转发可以大致分为两个阶段, 一个是动态方法解析, 而是完整的消息转发, 那么接下来先看第一个阶段:

动态方法解析

在这里, 就不得不提到OC的强大之处, 因为OC是动态语言, 因此消息的解析是运行时完成的, 还记得前一章节提到的isa指针, 那么动态方法解析的时候就需要使用到isa指针了.

首先消息发送之后, 先查询接受者, 也就是通过isa指针查找相对应的method_list分发表, 如果没有查找到指定的selector, 那么会接着延superClass继续查找, 直到查到根类, 如果这个过程中该选择子可以被执行了, 那么动态解析也就结束了, 如果不可以, 那么接下来就看当前类能否动态添加方法, 来处理这个未知的选择子了.

对象收到无法解读的消息之后, 会调用所属类的下列方法:

//实力方法
+ (BOOL)resolveInstanceMethod:(SEL)sel

//类方法
+ (BOOL)resolveClassMethod:(SEL)sel

在这里其实我们还是可以处理这个未知的选择子, 通过动态添加方法的形式, 处理未知的选择子, 这也是第一个阶段, 到最后runtime机制给我们提供的处理未知选择子的方法.

大致可以这样解决:

void addMethod(id obj, SEL _cmd)
{
    NSLog(@"Doing add method");
    NSLog(@"%@", obj);
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(clickAction:)) {
        class_addMethod([self class],sel,(IMP)addMethod,"v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

通过动态添加方法的形式来处理这个未知的选择子, 到这里第一阶段的工作也就基本结束了, 在第一阶段中, 其实我们已经可以处理未知的选择子了, 当然如果没有在第一阶段处理掉, 其实runtime还是给了我们第二次处理的机会, 接下来就说说完整的消息转发阶段:

完整的消息转发

其实,这个阶段也可以分为两小节来看待, 首先就是实现备援接受者, 这个其实跟前一个阶段的实现形式差不多, 大致实现如下:

- (id)forwardingTargetForSelector:(SEL)aSelector{
    TargetObject *object = [TargetObject new];
    if (aSelector == @selector(clickAction:) && [object respondsToSelector:aSelector]) {
        return object;
    }

    return [super forwardingTargetForSelector:aSelector];
}

这种处理方式, 其实更像向备援接受者”借用”了这个方法, 其实仔细想想, 这个跟继承的概念有点像, 但又不一样, 因为当前类没有继承备援类, 但是却可以使用备援类中的方法, 而继承也是可以这样的, 如果当前类有多个备援接受者, 其实就可以近似看成”多继承”类理解.

还有一点需要注意的是, 如果当前类借用了备援接受者中的很多方法来处理未知的选择子, 那么其实就可以直接继承来实现了, 因为通过动态消息解析的方式是比较消耗资源的, 因此, 建议这种情况下, 直接继承实现.

当着一部分还无法处理是, 接下来就需要启动完整的消息转发机制了, 此步骤回调用下列方法转发:

- (void)forwardInvocation:(NSInvocation *)anInvocation

这一阶段首先会创建Invocation对象, 将于未处理选择子相关的信息装在里面, 包括: 选择子, 目标以及参数等, 大致解决方式如下:

//首先需要their method  signatures
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    TargetObject *object = [TargetObject new];
    return [object methodSignatureForSelector:aSelector];
}

//然后, 重定向
-(void)forwardInvocation:(NSInvocation *)invocation
{  
    SEL invSEL = invocation.selector;    
    if([altObject respondsToSelector:invSEL]) {        
        [invocation invokeWithTarget:altObject];    
    } else {        
        [self doesNotRecognizeSelector:invSEL];    
    }
}

这个方法其实比较简单, 只是改变了调用目标, 是的消息在新目标上执行, 这其实跟备援接受者的实现方式类似, 都是讲未知的选择子转给可以处理的对象来处理.

下面这张图描述了消息转发机制处理消息的每个步骤:

这里写图片描述

该图选自Effective Objective-C 2.0 这本书, 建议大家阅读一下, 里面有很多高效开发的知识点, 还有一些很深入的讲解.

上图中可以看出, 在整个消息转发机制中, 每个阶段都可以处理未知的选择子, 但是步骤越往后, 处理消息的代价就越大, 最好是能够在第一步就处理掉, 这样的话, 其实代价相对较小.

总结

本文主要讲解了OC中动态消息转发的各个阶段, 也主要体现了runtime机制的强大之处, 通过运行期的动态方法解析, 我们可以动态添加需要的方法到类中, 也可以使用备援接受者来处理, 最后还可以通过完整的消息转发机制, 来处理未知消息.

因此, runtime机制使得我们的程序更健壮, 而且更灵活, 可以再运行期做更多的事情.

参考:
Objective-C Runtime Programming Guide
理解 Objective-C Runtime

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值