IOS 消息转发

最近在看消息转发的资料,发现大部分都是理论知识,很少有完整的代码。现在以代码的形式形象的解释一下:

用Xcode创建一个工程

1.正常方法调用

创建一个类Person 代码如下

Person.h代码如下:

#import <Foundation/Foundation.h>@interface Person : NSObject- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithUserName:(NSString *)userName;- (void)logUserName;@end

 

Person.m代码如下:

#import "Person.h"@interface Person()@property (nonatomic, copy) NSString *userName;@end@implementation Person- (instancetype)initWithUserName:(NSString *)userName
{self = [super init];if (self) {_userName = [userName copy];}return self;
}- (void)logUserName
{NSLog(@"userName = %@", self.userName);
}@end

 

ViewController.m代码如下:

#import "ViewController.h"
#import "Person.h"@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// Do any additional setup after loading the view, typically from a nib.Person *person = [[Person alloc] initWithUserName:@"小王"];[person logUserName];
}- (void)didReceiveMemoryWarning {[super didReceiveMemoryWarning];// Dispose of any resources that can be recreated.
}@end

运行工程结果为:

2017-03-01 22:47:07.296 RunTimeDemo[24364:1776826] userName = 小王

结果正常

2. unrecognized selector 情况

把Person.m 的logUserName方法删除,此时Person.m 的代码如下:

#import "Person.h"@interface Person()@property (nonatomic, copy) NSString *userName;@end@implementation Person- (instancetype)initWithUserName:(NSString *)userName
{self = [super init];if (self) {_userName = [userName copy];}return self;
}@end

运行工程会出现 如下结果

2017-03-01 23:06:25.788 RunTimeDemo[24729:1788071] -[Person logUserName]: unrecognized selector sent to instance 0x608000018e00
2017-03-01 23:06:25.796 RunTimeDemo[24729:1788071] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person logUserName]: unrecognized selector sent to instance 0x608000018e00'

假如不在Person类中实现logUsrName这个方法,也可以让工程正常运行,此时就涉及到消息转发,Objective-C运行时会给出三次拯救程序崩溃的机会。

如下:

(1).Method resolution

objc运行时会调用+resolveInstanceMethod:或者 +resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数,那运行时系统就会重新启动一次消息发送的过程,否则 ,运行时就会移到下一步,消息转发(Message Forwarding)。

(2).Fast forwarding

如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会。 只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。否则,就会继续Normal Fowarding。 这里叫Fast,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但下一步转发会创建一个NSInvocation对象,所以相对更快点。

(3).Normal forwarding

这一步是Runtime最后一次给你挽救的机会。首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象。

 

下面就验证一下上面所对应的函数的执行顺序:

Person.m的代码如下:

#import "Person.h"#define LOG_ClassAndFunctionName  NSLog(@"%@ %s", [self class], __func__)@interface Person()@property (nonatomic, copy) NSString *userName;@end@implementation Person- (instancetype)initWithUserName:(NSString *)userName
{self = [super init];if (self) {_userName = [userName copy];}return self;
}+ (BOOL)resolveInstanceMethod:(SEL)sel
{LOG_ClassAndFunctionName;return [super resolveInstanceMethod:sel];
}- (id)forwardingTargetForSelector:(SEL)aSelector
{LOG_ClassAndFunctionName;return [super forwardingTargetForSelector:aSelector];
}- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{LOG_ClassAndFunctionName;return [super methodSignatureForSelector:aSelector];
}- (void)forwardInvocation:(NSInvocation *)anInvocation
{LOG_ClassAndFunctionName;[super forwardInvocation:anInvocation];}- (void)doesNotRecognizeSelector:(SEL)aSelector
{LOG_ClassAndFunctionName;[super doesNotRecognizeSelector:aSelector];
}@end

 

运行结果为:

2017-03-02 10:16:37.855 RunTimeDemo[35098:2456660] Person +[Person resolveInstanceMethod:]
2017-03-02 10:16:37.856 RunTimeDemo[35098:2456660] Person -[Person forwardingTargetForSelector:]
2017-03-02 10:16:37.856 RunTimeDemo[35098:2456660] Person -[Person methodSignatureForSelector:]
2017-03-02 10:16:37.857 RunTimeDemo[35098:2456660] Person +[Person resolveInstanceMethod:]
2017-03-02 10:16:37.858 RunTimeDemo[35098:2456660] Person -[Person doesNotRecognizeSelector:]
2017-03-02 10:16:37.859 RunTimeDemo[35098:2456660] -[Person logUserName]: unrecognized selector sent to instance 0x600000003d40

 

运行结果正好验证了上述说明, 最后找不到实现会调用doesNotRecognizeSelector方法

那如何做才能不让其Crash呢?

做法是重写刚才说的那几个方法,那我们就倒着重写上面的方法。

3.重写方法来拯救Crash

1.在methodSignatureForSelector方法拦截 新建一个类ForwardClass,代码如下,具体看注释

#import "Person.h"#define LOG_ClassAndFunctionName  NSLog(@"%@ %s", [self class], __func__)@interface ForwardClass : NSObject- (void)logUserName;@end@implementation ForwardClass- (void)forwardLogUserName:(NSString *)userName;
{LOG_ClassAndFunctionName;NSLog(@"userName = %@",userName);
}@end@interface Person()@property (nonatomic, copy) NSString *userName;
@property (nonatomic, strong) ForwardClass *forwardClass;@end@implementation Person- (instancetype)initWithUserName:(NSString *)userName
{self = [super init];if (self) {_userName = [userName copy];_forwardClass = [[ForwardClass alloc] init];}return self;
}+ (BOOL)resolveInstanceMethod:(SEL)sel
{LOG_ClassAndFunctionName;return [super resolveInstanceMethod:sel];
}- (id)forwardingTargetForSelector:(SEL)aSelector
{LOG_ClassAndFunctionName;return [super forwardingTargetForSelector:aSelector];
}- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{LOG_ClassAndFunctionName;NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];if (!signature) {/// 方法签名为nil,就生成一个forwardClass的方法签名并制定selectorsignature = [self.forwardClass methodSignatureForSelector:@selector(forwardLogUserName:)];}return signature;
}- (void)forwardInvocation:(NSInvocation *)anInvocation
{LOG_ClassAndFunctionName;/// 重定向方法
    [anInvocation setSelector:@selector(forwardLogUserName:)];/// 假如调用的函数需要参数的话,在这里调用anInvocation的setArgument:atIndex:方法来进行设置; 指定参数,以指针方式,并且第一个参数的起始index是2,因为index为1,2的分别是self和selectorNSString *userName = self.userName;[anInvocation setArgument:&userName atIndex:2];/// ForwardClass 接受转发的消息
    [anInvocation invokeWithTarget:[[ForwardClass alloc] init]];
}- (void)doesNotRecognizeSelector:(SEL)aSelector
{LOG_ClassAndFunctionName;
}@end

 

运行结果:

2017-03-02 10:47:14.229 RunTimeDemo[36473:2479172] Person +[Person resolveInstanceMethod:]
2017-03-02 10:47:14.229 RunTimeDemo[36473:2479172] Person -[Person forwardingTargetForSelector:]
2017-03-02 10:47:14.229 RunTimeDemo[36473:2479172] Person -[Person methodSignatureForSelector:]
2017-03-02 10:47:14.230 RunTimeDemo[36473:2479172] Person +[Person resolveInstanceMethod:]
2017-03-02 10:47:14.230 RunTimeDemo[36473:2479172] Person -[Person forwardInvocation:]
2017-03-02 10:47:14.230 RunTimeDemo[36473:2479172] ForwardClass -[ForwardClass forwardLogUserName:]
2017-03-02 10:47:14.230 RunTimeDemo[36473:2479172] userName = 小王

此时不Crash了,我们让其执行了forwardClass 中的forwardLogUserName方法。

2.在forwardingTargetForSelector处拦截,转发给其他的类,执行对应的方法。代码如下

#import "Person.h"#define LOG_ClassAndFunctionName  NSLog(@"%@ %s", [self class], __func__)@interface ForwardClass : NSObject- (void)logUserName;@end@implementation ForwardClass- (void)forwardLogUserName:(NSString *)userName;
{LOG_ClassAndFunctionName;NSLog(@"userName = %@",userName);
}- (void)logUserName
{LOG_ClassAndFunctionName;
}@end@interface Person()@property (nonatomic, copy) NSString *userName;
@property (nonatomic, strong) ForwardClass *forwardClass;@end@implementation Person- (instancetype)initWithUserName:(NSString *)userName
{self = [super init];if (self) {_userName = [userName copy];_forwardClass = [[ForwardClass alloc] init];}return self;
}+ (BOOL)resolveInstanceMethod:(SEL)sel
{LOG_ClassAndFunctionName;return [super resolveInstanceMethod:sel];
}- (id)forwardingTargetForSelector:(SEL)aSelector
{LOG_ClassAndFunctionName;id fowarding = [super forwardingTargetForSelector:aSelector];if (!fowarding) {/// 进行拦截,让ForwardClass的示例进行执行aSelector方法fowarding = [[ForwardClass alloc] init];}return fowarding;
}- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{LOG_ClassAndFunctionName;NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];if (!signature) {/// 方法签名为nil,就生成一个forwardClass的方法签名并制定selectorsignature = [self.forwardClass methodSignatureForSelector:@selector(forwardLogUserName:)];}return signature;
}- (void)forwardInvocation:(NSInvocation *)anInvocation
{LOG_ClassAndFunctionName;/// 重定向方法
    [anInvocation setSelector:@selector(forwardLogUserName:)];/// 假如调用的函数需要参数的话,在这里调用anInvocation的setArgument:atIndex:方法来进行设置; 指定参数,以指针方式,并且第一个参数的起始index是2,因为index为1,2的分别是self和selectorNSString *userName = self.userName;[anInvocation setArgument:&userName atIndex:2];/// ForwardClass 接受转发的消息
    [anInvocation invokeWithTarget:[[ForwardClass alloc] init]];
}- (void)doesNotRecognizeSelector:(SEL)aSelector
{LOG_ClassAndFunctionName;
}@end

 

运行结果为:

2017-03-02 12:35:02.033 RunTimeDemo[41433:2552894] Person +[Person resolveInstanceMethod:]
2017-03-02 12:35:02.033 RunTimeDemo[41433:2552894] Person -[Person forwardingTargetForSelector:]
2017-03-02 12:35:02.034 RunTimeDemo[41433:2552894] ForwardClass -[ForwardClass logUserName]

3.在resolveInstanceMethod处拦截,动态的为类添加相应的方法。代码如下:

#import "Person.h"
#import <objc/runtime.h>#define LOG_ClassAndFunctionName  NSLog(@"%@ %s", [self class], __func__)@interface ForwardClass : NSObject- (void)logUserName;@end@implementation ForwardClass- (void)forwardLogUserName:(NSString *)userName;
{LOG_ClassAndFunctionName;NSLog(@"userName = %@",userName);
}- (void)logUserName
{LOG_ClassAndFunctionName;
}@end@interface Person()@property (nonatomic, copy) NSString *userName;
@property (nonatomic, strong) ForwardClass *forwardClass;@end@implementation Personvoid dynamicMethodIMP(id self, SEL _cmd)
{LOG_ClassAndFunctionName;
}- (instancetype)initWithUserName:(NSString *)userName
{self = [super init];if (self) {_userName = [userName copy];_forwardClass = [[ForwardClass alloc] init];}return self;
}+ (BOOL)resolveInstanceMethod:(SEL)sel
{LOG_ClassAndFunctionName;if (sel == @selector(logUserName)) {/// 动态的为这个类去添加方法class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");return YES;}return [super resolveInstanceMethod:sel];
}- (id)forwardingTargetForSelector:(SEL)aSelector
{LOG_ClassAndFunctionName;id fowarding = [super forwardingTargetForSelector:aSelector];if (!fowarding) {/// 进行拦截,让ForwardClass的示例进行执行aSelector方法fowarding = [[ForwardClass alloc] init];}return fowarding;
}- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{LOG_ClassAndFunctionName;NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];if (!signature) {/// 方法签名为nil,就生成一个forwardClass的方法签名并制定selectorsignature = [self.forwardClass methodSignatureForSelector:@selector(forwardLogUserName:)];}return signature;
}- (void)forwardInvocation:(NSInvocation *)anInvocation
{LOG_ClassAndFunctionName;/// 重定向方法
    [anInvocation setSelector:@selector(forwardLogUserName:)];/// 假如调用的函数需要参数的话,在这里调用anInvocation的setArgument:atIndex:方法来进行设置; 指定参数,以指针方式,并且第一个参数的起始index是2,因为index为1,2的分别是self和selectorNSString *userName = self.userName;[anInvocation setArgument:&userName atIndex:2];/// ForwardClass 接受转发的消息
    [anInvocation invokeWithTarget:[[ForwardClass alloc] init]];
}- (void)doesNotRecognizeSelector:(SEL)aSelector
{LOG_ClassAndFunctionName;
}@end

运行结果为:

2017-03-02 12:44:24.529 RunTimeDemo[41577:2561853] Person +[Person resolveInstanceMethod:]
2017-03-02 12:44:24.530 RunTimeDemo[41577:2561853] Person dynamicMethodIMP

总结:上面三次拦截更加形象的说明消息转发进行的次序。若某个类的实例调用一个没有实现的方法后。首先会调用

resolveInstanceMethod/resolveClassMethod方法,若没有添加相应的方法,其次就会调用forwardingTargetForSelector方法,若没有转发给其他对象,最后就调用methodSignatureForSelector方法,若方法签名为nil,Runtime则会发出-doesNotRecognizeSelector:消息。此时就Crash了。

上面的工程链接为: https://github.com/lidaojian/RunTimeDemo

 

================================================================

若有疑问请加本人QQ:610774281 微信:stephenli225。 一起探讨一起进步。。。。

转载于:https://www.cnblogs.com/lidaojian/p/6487110.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/386229.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网易资深Java架构师:java数组对象转为list集合

前言 现在刷抖音经常可以看到一些老外街坊&#xff0c;问他们最想把什么带回自己的国家&#xff0c;我听过很多的回答都是&#xff1a;淘宝&#xff0c;支付宝&#xff0c;美食&#xff0c;微信&#xff0c;外卖&#xff0c;高铁等等。 确实如此&#xff0c;随着国家的快速发…

夯实基础——P2084 进制转换

题目链接&#xff1a;https://www.luogu.org/problem/P2084 P2084 进制转换 题目背景 无 题目描述 今天小明学会了进制转换&#xff0c;比如&#xff08;10101&#xff09;2 &#xff0c;那么它的十进制表示的式子就是 : 1*2^40*2^31*2^20*2^11*2^0&#xff0c; 那么请你编程实…

网易资深Java架构师:java方法的定义和使用

前言 今年因为这个疫情&#xff0c;感觉这是从工作以来过的最久的一个年了&#xff0c;在家呆的时间不是一般的久&#xff0c;算一算有好几个月呢&#xff01;我大概是3月底快4月了才出门&#xff0c;投了超多的简历&#xff0c;天天面试面试面试面试面试面试面试…庆幸的是还…

PHP----学生管理系统

闲来无事花费两天时间写了份简易版的学生管理系统 源码地址:https://www.cnblogs.com/post/ReadAuth?blogId509327&PostId11333758&url%2Fbyczyz%2Fprotected%2Fp%2F11333758.html 转载于:https://www.cnblogs.com/byczyz/p/11333760.html

网易资深Java架构师:jdkjrejvm的区别和联系

前言 作为同时具备高性能、高可靠和高可扩展性的典型键值数据库&#xff0c;Redis不仅功能强大&#xff0c;而且稳定&#xff0c;理所当然地成为了大型互联网公司的首选。 众多大厂在招聘的时候&#xff0c;不仅会要求面试者能简单地使用Redis&#xff0c;还要能深入地理解底…

深度学习之开端备注

Adagrad //适合稀疏样本 RMSprop//借鉴Adagrad的思想&#xff0c;改进使得不会出现学习率越来越低的问题 由此可见Adadelta既不需要输入学习率等参数&#xff0c;而且表现得非常好&#xff01;&#xff01;但是我试了几次&#xff0c;这个优化器效果极差&#xff01;&#xff0…

网易资深Java架构师:疫情对java行业的影响分析

前言 在实际开发&#xff0c;Redis使用会频繁&#xff0c;那么在使用过程中我们该如何正确抉择数据类型呢&#xff1f;哪些场景下适用哪些数据类型。而且在面试中也很常会被面试官问到Redis数据结构方面的问题&#xff1a; Redis为什么快呢&#xff1f;为什么查询操作会变慢了…

ListView与.FindControl()方法的简单练习 #2 -- ItemUpdting事件中抓取「修改后」的值

原文出處 http://www.dotblogs.com.tw/mis2000lab/archive/2013/06/24/listview_itemupdating_findcontrol_20130624.aspx ListView与.FindControl()方法的简单练习 #2 -- ItemUpdting事件中抓取「修改后」的值 本文跟上一篇文章有关连&#xff0c;请依照顺序来练习&#xff1…

美团java研发岗二面:java静态方法存储在哪个区

思维导图 前言 在很多时候&#xff0c;我们都可以在各种框架应用中看到ZooKeeper的身影&#xff0c;比如Kafka中间件&#xff0c;Dubbo框架&#xff0c;Hadoop等等。为什么到处都看到ZooKeeper&#xff1f; 一、 前些年&#xff0c;互联网行业里对架构师这个岗位的标准还不是…

[学习之道] 修福不修慧,大象披璎珞; 修慧不修福,罗汉托空钵 (学习写程序,只靠补习上课吗?)...

这是我的备份&#xff0c;原文请看 http://www.dotblogs.com.tw/mis2000lab/archive/2014/09/17/learning-and_do-it_20140917.aspx [学习之道] 修福不修慧&#xff0c;大象披璎珞&#xff1b; 修慧不修福&#xff0c;罗汉托空钵 (学习写程序&#xff0c;只靠补习上课吗&#…

阿里P8亲自教你!mysql列转行

前言 今日博主听闻&#xff0c;现在很多培训出来的应届生薪资都赶上了摸爬滚打两三年的朋友&#xff0c;讲道理&#xff0c;这说不过去啊 作为同行来说&#xff0c;这个行业发展很快&#xff0c;技术更新很快&#xff0c;淘汰也很快&#xff0c;千万不要再找借口了&#xff0…

同步、异步、多线程

1、首先明确一点&#xff0c;对于单核CPU&#xff0c;任意一个时刻只有一个线程在运行。那么既然这样&#xff0c;多线程还有什么意义呢&#xff1f; 举例来说&#xff0c;现在只有一个人&#xff0c;要做好几个任务。单线程就是&#xff0c;任务一个一个地做&#xff0c;必须做…

阿里P8亲自教你!熬夜整理华为最新Java笔试题

前言 Mysql的锁机制确实非常重要&#xff0c;所以在这里做一个全面的总结整理&#xff0c;便于以后的查阅&#xff0c;也分享给大家。 Mysql的锁机制还是有点难理解的&#xff0c;所以这篇文章采用图文结合的方式讲解难点&#xff0c;帮助大家理解&#xff0c;讲解的主要内容…

JSP基础笔记

/** #####这部分也是笔记,用于记录JSP的相关内容* ###怎么用JSP###指令的写法* <% 指令名字%>* *### page指令 * language > 表明jsp页面中可以写java代码 * contentType > 其实即使说这个文件是什么类型&#xff0c;告诉浏览器我是什么内容类型&#xff0c;以及使…

阿里P8亲自讲解!javawhile循环语句用法

前言 作为一个已经毕业的计算机专业学长&#xff0c;其实几年大学走来还是挺感慨万千的。&#xff08;说明一下&#xff1a;一本&#xff0c;非958、211&#xff09; 老实说&#xff0c;上大学之前填志愿选专业的时候没有任何打算&#xff0c;就觉得学海熬到头了&#xff0c;向…

Cookie,Session基础知识

//这部分主要是CookieSession的笔记部分/** //获取来访的客户端类型String clientTyereq.getHeader("User-Agent");//如果是火狐浏览器&#xff0c;那么使用以下代码if(clientTye.contains("Firefox")){fileNameDownLoadUtil.base64EncodeFileName(fileNam…

阿里P8亲自讲解!java中级开发工程师需要掌握的技能

前言 关于技术人如何成长的问题&#xff0c;一直以来都备受关注&#xff0c;因为程序员职业发展很快&#xff0c;即使是相同起点的人&#xff0c;经过几年的工作或学习&#xff0c;会迅速拉开极大的差距&#xff0c;所以技术人保持学习&#xff0c;提升自己&#xff0c;才能够…

随笔--互联网进化论

不闻不若闻之&#xff1b;有的人士的理论引起了轰动&#xff0c;吾等小网民也来学学&#xff0c;没那么大脑袋&#xff0c;从不敢谈解读与批判。闻香而来&#xff0c;放屁而去。比如说互联网进化论。看看实践&#xff0c;互联网的膨胀式发展是不会停的&#xff0c;在中国的互联…

阿里P8亲自讲解!java分布式需要学什么技术

引言 最近项目上线的频率颇高&#xff0c;连着几天加班熬夜&#xff0c;身体有点吃不消精神也有些萎靡&#xff0c;无奈业务方催的紧&#xff0c;工期就在眼前只能硬着头皮上了。脑子浑浑噩噩的时候&#xff0c;写的就不能叫代码&#xff0c;可以直接叫做Bug。我就熬夜写了一个…

Javascript的this用法

出自&#xff1a;http://www.ruanyifeng.com/blog/2010/04/using_this_keyword_in_javascript.html this是Javascript语言的一个关键字。 它代表函数运行时&#xff0c;自动生成的一个内部对象&#xff0c;只能在函数内部使用。比如&#xff0c; function test(){ this.x 1; }…