iOS的消息转发机制详解
iOS开发过程中,有一类的错误会经常遇到,就是找不到所调用的方法,当然这类问题比较好解决,给当前对象或其父类对象添加该方法即可,使得编译器在编译时能正确找到该方法;或者,还有另外的方法,由于Objective-C是一门动态语言,我们也可以在运行期再给类添加该方法,一样可以解决该问题,而这就涉及了类的消息转发机制。
本文就主要来介绍一下iOS系统的消息转发机制,探究一下在调用一个方法时,如果本类中没有该方法时,对象究竟是如何进行消息转发的,来避免程序抛出异常。
异常现象
当调用的对象方法不存在,即使经过消息转发也不存在时,就会抛出下面的异常
-[Teacher playPiano]: unrecognized selector sent to instance 0x6000000114c0
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Teacher playPiano]: unrecognized selector sent to instance 0x6000000114c0'
解决办法
针对上述的异常问题,最简单的方法就是直接在类中添加playPiano方法,或者在其继承树中添加该方法,均可以解决该问题,所以这种方法再次不再赘述,下面介绍一下如何利用消息转发机制解决该问题。
消息转发是在运行时进行的,大致分为两个阶段:第一阶段是先检查接收者,看是否能通过runtime动态添加一个方法,来处理这个unknown selector的消息;第二阶段就是完整的消息转发机制,首先会先查看有没有其它对象能够处理该消息,如果没有,就把该消息的全部信息封装到NSInvocation对象中,看那个对象能否处理,如果还无法处理,就查看继承树中的类是否能够处理该消息,如果到NSObject之前都无法处理该消息,那么最后就会调用NSObject类的doesNotRecognizeSelector方法来抛出异常,表明调用的方法不存在。
1.动态方法解析
对象在收到无法处理的消息时,会调用下面的方法,前者是调用类方法时会调用,后者是调用对象方法时会调用
// 类方法专用
+ (BOOL)resolveClassMethod:(SEL)sel
// 对象方法专用
+ (BOOL)resolveInstanceMethod:(SEL)sel
在该方法中,需要给对象所属类动态的添加一个方法,并返回YES,表明可以处理
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSString *method = NSStringFromSelector(sel);
if ([@"playPiano" isEqualToString:method]) {
/**
添加方法
@param self 调用该方法的对象
@param sel 选择子
@param IMP 新添加的方法,是c语言实现的
@param 新添加的方法的类型,包含函数的返回值以及参数内容类型,eg:void xxx(NSString *name, int size),类型为:v@i
*/
class_addMethod(self, sel, (IMP)playPiano, "v");
return YES;
}
return NO;
}
2.备援接受者
经历了第一步后,如果该消息还是无法处理,那么就会调用下面的方法,查询是否有其它对象能够处理该消息
- (id)forwardingTargetForSelector:(SEL)aSelector
在这个方法里,我们需要返回一个能够处理该消息的对象
- (id)forwardingTargetForSelector:(SEL)aSelector
{
NSString *seletorString = NSStringFromSelector(aSelector);
if ([@"playPiano" isEqualToString:seletorString]) {
Student *s = [[Student alloc] init];
return s;
}
// 继续转发
return [super forwardingTargetForSelector:aSelector];
}
3.完整的消息转发
经历了前两步,还是无法处理消息,那么就会做最后的尝试,先调用methodSignatureForSelector:获取方法签名,然后再调用forwardInvocation:进行处理,这一步的处理可以直接转发给其它对象,即和第二步的效果等效,但是很少有人这么干,因为消息处理越靠后,就表示处理消息的成本越大,性能的开销就越大。所以,在这种方式下,会改变消息内容,比如增加参数,改变选择子等等。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
- (void)forwardInvocation:(NSInvocation *)anInvocation
下面是改变选择子的例子,比如我们直接调用的是playPiano方法,最后转发给了traval:方法,完整实例参考:MsgSendDemo
// 完整的消息转发
- (void)travel:(NSString*)city
{
NSLog(@"Teacher travel:%@", city);
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSString *method = NSStringFromSelector(aSelector);
if ([@"playPiano" isEqualToString:method]) {
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:@"];
return signature;
}
return nil;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
SEL sel = @selector(travel:);
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:@"];
anInvocation = [NSInvocation invocationWithMethodSignature:signature];
[anInvocation setTarget:self];
[anInvocation setSelector:@selector(travel:)];
NSString *city = @"北京";
// 消息的第一个参数是self,第二个参数是选择子,所以"北京"是第三个参数
[anInvocation setArgument:&city atIndex:2];
if ([self respondsToSelector:sel]) {
[anInvocation invokeWithTarget:self];
return;
} else {
Student *s = [[Student alloc] init];
if ([s respondsToSelector:sel]) {
[anInvocation invokeWithTarget:s];
return;
}
}
// 从继承树中查找
[super forwardInvocation:anInvocation];
}
iOS的消息转发机制给我们提供了更多的选择,来保证消息的正常传递,而了解这些具体的实现方法,则可以让我们的程序更加的健壮。
参考资料
NSMethodSignature和NSInvocation的用法
使用NSMethodSignature和NSInvocation实现消息转发
iOS的消息转发机制详解的更多相关文章
- IOS 触摸事件分发机制详解
欢迎大家前往云+社区,获取更多腾讯海量技术实践干货哦~ 作者:MelonTeam 前言 很多时候大家都不关心IOS触摸事件的分发机制的实现原理,当遇到以下几种情形的时候你很可能抓破头皮都找不到解决方案 ...
- iOS 消息转发机制
这篇博客的前置知识点是 OC 的消息传递机制,如果你对此还不了解,请先学习之,再来看这篇.这篇博客我尝试用口语的方式像讲述 PPT 一样给大家讲述这个知识点. 我们来思考一个问题,如果对象在收到无法解 ...
- iOS runtime探究(二): 从runtime開始深入理解OC消息转发机制
你要知道的runtime都在这里 转载请注明出处 http://blog.csdn.net/u014205968/article/details/67639289 本文主要解说runtime相关知识, ...
- iOS Runtime的消息转发机制
前面我们已经讲解Runtime的基本概念和基本使用,如果大家对Runtime机制不是很了解,可以先看一下以前的博客,会对理解这篇博客有所帮助!!! Runtime基本概念:https://www.cn ...
- iOS消息转发机制
iOS消息转发机制 “消息派发系统”(message-dispatch system) 若想令类能够理解某条消息,我们必须实现出对应的方法才行.但是,在编译器向类发送其无法解读的消息时并不会报错,因为 ...
- MVVMLight消息通知实现机制详解(二)
接上文 MVVMLight消息通知实现机制详解(一) 该工具的内部主要逻辑是以字典模式进行储存持有订阅对象设置的传入参数Type类型.Key值.Action.Target(订阅对象本身) 在发生订阅事 ...
- MVVMLight消息通知实现机制详解(一)
最近对委托.事件的订阅使用的太多,订阅与被订阅之间的绑定约束非常...麻烦,所以翻了下MVVMLight源码找出这段可以拿出来用的部分,详情见下: 一.开发中遇到的问题: 场景1:ClassA中存在事 ...
- iOS中MVC等设计模式详解
iOS中MVC等设计模式详解 在iOS编程,利用设计模式可以大大提高你的开发效率,虽然在编写代码之初你需要花费较大时间把各种业务逻辑封装起来.(事实证明这是值得的!) 模型-视图-控制器(MVC)设计 ...
- 浏览器 HTTP 协议缓存机制详解
最近在准备优化日志请求时遇到了一些令人疑惑的问题,比如为什么响应头里出现了两个 cache control.为什么明明设置了 no cache 却还是发请求,为什么多次访问时有时请求里带了 etag, ...
随机推荐
- 使用freemarker模板生成word文档
项目中最近用到这个东西,做下记录. 如下图,先准备好一个(office2003)word文档当做模板.文档中图片.姓名.性别和生日已经使用占位符代替,生成过程中将会根据实际情况进行替换. 然后将wor ...
- CentOS升级Python到2.7版本
查看python的版本 1 python -V Python 2.4.3 1.先安装GCC 1 yum -y install gcc 2.下载Python-2.7.2 1 wget http://py ...
- 页面打印(js/jquery)
1.js实现(可实现局部打印) <html> <title>js打印</title> <head></head><body> ...
- Struts2国际化信息机制
国际化信息机制 (三种 Action范围. Package范围. 全局) 1. 全局国际化配置信息文件 全局国际化文件,对所有Action 生效,任何程序都可以访问到,需要在struts.xml 配 ...
- 使用joda-time工具类 计算时间相差多少 天,小时,分钟,秒
下面程序使用了两种方法计算两个时间相差 天,小时,分钟,秒 package jodotest; import java.text.ParseException; import java.text.Si ...
- 设计模式 - 观察者模式(JDK)
定义:观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新. 对象: 抽象主题角色:每个抽象主题角色都可以有任意数量的观察者.抽象主题提供可 ...
- Python如何实现单步调试
遇到大型python项目,如何定位问题和监控程序的运行状态是一个程序员必须掌握的技能,今天小编为你带来python程序的单步调试方法,方便易用,简单易记! 首先你需要在所调试程序的开头中:import ...
- 第一课 1) 控制div属性 总结
点击按钮变换属性: <!DOCTYPE html> <html lang="en"> <head> <meta charset=" ...
- 如何通过android代码获取LTE信息?
最近为了成功得到LTE的信号强度,尝试了很多种方法: (1)通过解析signalstrength字符串,但是不同手机设备获得的字符串排列顺序不同,代码如下: private PhoneStateLis ...
- Docker进阶使用1
容器间共享文件 Docker 的容器和外部环境是相对隔离的,并且容器是一次性的,运行结束后并不会有任何的持久化的文件或者数据.所以当我们需要做应用数据的持久化,或者保留应用的日志文件时,我们需要用到 ...