OC:浅析Runtime中消息转发机制
一、介绍
OC是一门动态性语言,其实现的本质是利用runtime机制。在runtime中,对象调用方法,其实就是给对象发送一个消息,也即objc_msgSend()。在这个消息发送的过程中,系统会进行一系列的操作,最终实现消息的成功转发或者异常的抛出。这个传递的过程就是消息的转发。
消息转发过程:1、动态解析 2、快转发(接收者重定向) 3、慢转发(完整转发,方法重定向)

二、示例
在Person类中.h文件中声明了一个吃的方法: eat ,但是.m中没有具体的实现。
@interface Person : NSObject
-(void)eat;
@end
然后在控制器中创建Person对象调用eat,发现crash了。
- (void)viewDidLoad {
[super viewDidLoad];
Person *person = [[Person alloc] init];
[person eat];
}
实现结果和原因如下
crash: '-[Person eat]: unrecognized selector sent to instance 0x60000366f310
reason: methodSignatureForSelector方法中,返回了一个空的对应方法签名,最终导致程序报错崩溃
此时,针对这种情况,消息就进入了转发流程。系统允许我们在Person中通过重写上述的方法进行消息转发。
注意:三个阶段的方法不一定都要重写,如果上一步重写的方法完成了消息的发送,程序就结束了。
第一阶段:动态解析,也即动态添加方法(可以是eat方法实现,也可以是其他方法的实现)
1、在Person类中实现一个方法addEatMethod
-(void)addEatMethod {
NSLog(@"------addEatMethod-----------");
}
2、在Person类中重写resolveInstanceMethod,将addEatMethod通过class_addMethod添加给当前对象
//动态解析:动态添加方法
/*
1、当前对象调用方法,在runtime中,其实就是给当前对象发送一个消息,也即objc_msgSend()。
2、首先系统会通过当前对象的isa指针指向它的类,然后在类的方法缓存中查找是否有此方法,如果有,则取出调用,
如果没有,就去方法列表中查找,如果有,则调用,如果也没有,接着去父类中重复之前的操作进行查找。最后都没有找到该方法时,进入消息的转发流程。
3、此时系统提供给调用者一个机会,重写resolveInstanceMethod方法,进行动态解析,要么不处理沿继承树传递,要么去动态添加方法的实现,完成消息的发送。
*/
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *methodName = NSStringFromSelector(sel);
if ([methodName isEqualToString:@"eat"]) {
SEL selName = NSSelectorFromString(@"addEatMethod");
Method method = class_getInstanceMethod(self, selName);
return class_addMethod(self, sel, method_getImplementation(method), "v@:");
}
return [super resolveInstanceMethod:sel];
}
3、程序没有崩溃,打印结果如下
-- ::33.197134+ 消息转发[:] ------addEatMethod-----------
第二阶段:快转发,也即接收者重定向
1、在另一个类中实现eat方法,例如在OtherPerson类中实现了eat方法
#import "OtherPerson.h"
@implementation OtherPerson
-(void)eat {
NSLog(@"__%s__: OtherPerson_吃饭",__func__);
}
@end
2、在Person类中重写forwardingTargetForSelector方法,返回新的消息接收者
//快转发:接收者重定向
/*
如果调用者重写了resolveInstanceMethod,但是并没有动态的去添加方法的实现,消息就进入快速转发阶段。
系统提供给调用者去查找实现了该方法的其他接收者,也即重写forwardingTargetForSelector。在该方法中,调用者要么重新
指定消息的接收者完成消息的发送,要么仍然沿继承树传递。
*/
- (id)forwardingTargetForSelector:(SEL)aSelector { NSString *methodName = NSStringFromSelector(aSelector);
if ([methodName isEqualToString:@"eat"]){
return [[OtherPerson alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
3、程序也没有崩溃,打印结果如下
-- ::10.542697+ 消息转发[:] __-[OtherPerson eat]__: OtherPerson_吃饭
第三阶段:慢转发,也即完整转发,消息重定向
1、在Person类中重写methodSignatureForSelector手动生成方法签名,目的是验证选择器的合法性
2、在Person类中重写forwardInvocation指定其他的接收者(也可以修改实现方法,修改响应对象)
//慢转发:完整转发,消息重定向
/*
调用者在resolveInstanceMethod和forwardingTargetForSelector中都没有对消息进行处理,消息就进入慢速转发阶段。
系统提供给调用最后一次机会处理消息,重写methodSignatureForSelector和forwardInvocation方法,
在methodSignatureForSelector中对消息的实现进行签名,在forwardInvocation中消息接收者根据签名完成消息转发。
*/
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { NSString *methodName = NSStringFromSelector(aSelector);
if ([methodName isEqualToString:@"eat"]) {
//手动生成方法签名
//NSMethodSignature *signature = [NSMehtodSignature methodSignatureForSelector:aSelector];
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:"];
return signature;
}
return [super methodSignatureForSelector:aSelector];
} - (void)forwardInvocation:(NSInvocation *)anInvocation { //获取签名信息
SEL selector = anInvocation.selector; //新建需要转发消息的对象
OtherPerson *person = [OtherPerson new]; //判断是否响应
if ([person respondsToSelector:selector]) { //若可以响应,则将消息转发给该对象处理
[anInvocation invokeWithTarget:person]; //也可以修改实现方法,修改响应对象
//selector = @selector(addEatMethod);
//[anInvocation setSelector:selector];
//[anInvocation invokeWithTarget:self];
}else {
[super forwardInvocation:anInvocation];
}
}
3、程序仍然没有崩溃,打印结果
-- ::13.216412+ 消息转发[:] __-[OtherPerson eat]__: OtherPerson_吃饭
最后:异常捕捉
1、在Person类中重写doesNotRecognizeSelector方法。
//慢转发:完整转发,消息重定向
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { NSString *methodName = NSStringFromSelector(aSelector);
if ([methodName isEqualToString:@"eat"]) {
//手动生成方法签名
//NSMethodSignature *signature = [NSMethodSignature methodSignatureForSelector:aSelector];
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:"];
return signature;
}
return [super methodSignatureForSelector:aSelector];
}//消息未识别
/*
动态解析、快转发、慢转发这三个过程中调用者都没有处理消息时,系统会调用doesNotRecognizeSelector方法,抛出异常
调用者也可以重写这个方法,对异常做自己的处理操作,防止程序崩溃,提高app使用体验。
推荐用在线上版本,开发环境下,还是要把问题抛出来,以便解决。
*/
-(void)doesNotRecognizeSelector:(SEL)aSelector {
NSLog(@"----Person没有实现eat方法----- ");
}
2、程序依然没有崩溃,打印结果 (注意:方法签名必须要有,不然就会崩溃)
-- ::01.136673+ 消息转发[:] ----Person没有实现eat方法-----
OC:浅析Runtime中消息转发机制的更多相关文章
- iOS Runtime的消息转发机制
前面我们已经讲解Runtime的基本概念和基本使用,如果大家对Runtime机制不是很了解,可以先看一下以前的博客,会对理解这篇博客有所帮助!!! Runtime基本概念:https://www.cn ...
- iOS runtime探究(二): 从runtime開始深入理解OC消息转发机制
你要知道的runtime都在这里 转载请注明出处 http://blog.csdn.net/u014205968/article/details/67639289 本文主要解说runtime相关知识, ...
- 理解Objective-C Runtime(三)消息转发机制
消息转发机制概述 上一篇博客消息传递机制中讲解了Objective-C中对象的「消息传递机制」.本文需要讲解另外一个重要问题:当对象受到无法处理的消息之后会发生什么情况? 显然,若想令类能理解某条消息 ...
- runtime之消息转发
前言 在上一篇文章中我们初尝了runtime的黑魔法,可以在程序编译阶段就获取到成员变量的名字,特性以及动态的给对象增加属性等等,在接下来中我们进一步了解OC的消息发送机制.如果之前没接触过runti ...
- iOS 消息转发机制
这篇博客的前置知识点是 OC 的消息传递机制,如果你对此还不了解,请先学习之,再来看这篇.这篇博客我尝试用口语的方式像讲述 PPT 一样给大家讲述这个知识点. 我们来思考一个问题,如果对象在收到无法解 ...
- iOS的消息转发机制详解
iOS开发过程中,有一类的错误会经常遇到,就是找不到所调用的方法,当然这类问题比较好解决,给当前对象或其父类对象添加该方法即可,使得编译器在编译时能正确找到该方法:或者,还有另外的方法,由于Objec ...
- Effective Objective-C 2.0 — 第12条:理解消息转发机制
11 条讲解了对象的消息传递机制 12条讲解对象在收到无法解读的消息之后会发生什么,就会启动“消息转发”(message forwarding)机制, 若对象无法响应某个选择子,则进入消息转发流程. ...
- iOS消息转发机制
iOS消息转发机制 “消息派发系统”(message-dispatch system) 若想令类能够理解某条消息,我们必须实现出对应的方法才行.但是,在编译器向类发送其无法解读的消息时并不会报错,因为 ...
- runtime消息转发机制
Objective-C 扩展了 C 语言,并加入了面向对象特性和 Smalltalk 式的消息传递机制.而这个扩展的核心是一个用 C 和 编译语言 写的 Runtime 库.它是 Objective- ...
随机推荐
- 腾讯 Techo 开发者大会首发来袭!云原生中间件技术实践等你来!
腾讯 Techo 开发者大会是由腾讯云发起的面向全球开发者和技术爱好者的年度盛会,2019 年 11 月 6 日 - 7 日将在北京嘉里大酒店首次召开. 作为一个专注于前沿技术研讨的非商业大会,Tec ...
- 使用 Anydesk 5.1 TCP 通道(端口映射)功能从外网方便访问内网的 web/数据库等资源
Anydesk 5.1 带来一个新的功能:TCP 通道,在家办公时,通过互联网进行远程桌面连接到公司电脑,可以将家用电脑的某个端口,映射到公司网络的某个电脑( IP + 端口),不局限于被远程桌面连接 ...
- Ubuntu16.04 GTX750安装CUDA9.0,Pytorch,Anaconda教程
Ubuntu16 GTX750安装CUDA9.0,Pytorch,Anaconda教程 安装前警告 不要使用Ubuntu18! 不要使用Ubuntu18! 不要使用Ubuntu18! 务必重装成Ubu ...
- C#关于MySQL中文乱码问题
本人在写一个测试demo的时候,遇到一个问题就是添加的中文数据在数据库定义的明明是varchar类型,但是显示出来还是乱码,不过还是自己粗心导致的问题. 以下三种方式可以自查一下: 1. 首先检查 ...
- vue-router之前端路由的学习总结
什么是路由 路由就是通过互联网把信息从源地址传输到目的地的活动 --维基百科 举例路由器: 路由器提供了两种机制:路由和转送 路由是决定数据包从来源到目的地的路径 转送将输入端的数据转移到合适的输出端 ...
- 编码方式ASCII、GBK、Unicode、UTF-8比较
文章内容深度较浅,详细了解可到下链接:https://blog.csdn.net/QuinnNorris/article/details/78705723; 总结了以下几种编码方式: ASCII.GB ...
- os.path.isfile()的正确用法(正确用法)
之前网上查找os.path.isfile( )的使用:发现有些是错误的,主要原因是,传入的参数是相对路径,不是绝对路径. 但是,经过我的实验发现:os.path.isfile( )需要传入的参数是绝对 ...
- MVC邮箱验证
post请求 [HttpPost] public void Email(Models.Email m,string Txt) { if (Txt!= Session["yzm"]. ...
- Go-获取变量数据类型
package main import ( "fmt" "reflect" //这个包里的TypeOf方法获取变量数据类型 ) func main(){ b : ...
- 一则sql优化实现接口耗时降低30倍的优化案例
业务场景: 也测的业务,如上图,通过捕获业务的涉及的接口如下: 查询接口耗时大于7s,已经是非常的慢 经验提示: 一般接口响应时间慢的问题,最简单的方式就是监控接口相关的sql是否存在问题 开启mys ...