文档章节

iOS-消息转发机制

麦兜卖鱼丸
 麦兜卖鱼丸
发布于 2016/03/27 14:45
字数 1417
阅读 75
收藏 0
点赞 1
评论 0

前言

其他编程语言所说的函数调用,在oc中被称作为发送消息;消息转发的作用,开发者可以在找不到的方法的情况下,可以通过动态添加方法或者是消息转发,确定本次发送消息是否成功,通过这样的特性开发者可以做很多必要的善后处理。

 

ios消息转发的作用;

(1)对象发送了未实现的消息,可以通过消息转发机制,转移到其他对象去处理该消息;

(2)除了协议、类别,也可以通过消息转发机制实现多继承;

 

实现ios消息转发前的准备工作;

(1)为了配合下面的实例预先定义好SleepViewController,实现sleep和eat方法;

#import "SleepViewController.h"

@interface SleepViewController ()

@end

@implementation SleepViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/
- (void)sleep
{
    NSLog(@"excute SleepViewController sleep");
}

- (void)eat
{
    NSLog(@"ok,begin eat");
}
@end

 

ios消息转发的过程;

(1)动态方法解析,比如执行[self performSelector:@selector(name)];在本类父类以及NSObject都未能查找到该方法,就是先判断是否需要动态添加该方法,本例运用runtime动态添加了testClassName方法,执行结果会输出"add testClassName method";

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(name)) {
        class_addMethod(self, sel, class_getMethodImplementation([self class], @selector(testClassName:)), method_getTypeEncoding(class_getInstanceMethod([self class], @selector(testClassName:))));
        return YES;
    }else if (sel == @selector(sleep)){
        return NO;
    }
   return [super resolveInstanceMethod:sel];
}
- (void)testClassName:(NSString *)string
{
    NSLog(@"add testClassName method");
}

(2)如果(BOOL)resolveInstanceMethod:(SEL)sel或者(BOOL)resolveClassMethod:(SEL)sel返回NO,消息转发就会进入转发机制;比如执行[self performSelector:@selector(sleep)],这个sleep方法在SleepViewController声明并实现,结果输出"excute SleepViewController sleep";

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    NSLog(@"not found method sleep");
    if (aSelector == @selector(sleep)) {
        return [[SleepViewController alloc] init];
    }else{
        NSLog(@"not found eat");
        return nil;
    }
}

(3)当重定向还不作处理的时候,这消息转发机制就会出发,这个时候就会调用- (void)forwardInvocation:(NSInvocation *)anInvocation这个方法;但是在这个方法执行之前会先调用methodSignatureForSelector方法,所以我们也要复写这个方法,否则就会报异常;所以我们要重写这个方法。比如执行[self performSelector:@selector(eat)];既没有动态添加该方法,也没有重定向该方法,所以执行了一下的两个方法,做最后的逻辑处理;

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSLog(@"method name -- %@",NSStringFromSelector(aSelector));
    if (aSelector == @selector(eat)) {
        //签名方法;
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    
    return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    SEL sele = [anInvocation selector];
    SleepViewController *slee = [[SleepViewController alloc] init];
    if ([slee respondsToSelector:sele]) {
        //转发到SleepViewController执行eat方法;
        [anInvocation invokeWithTarget:slee];
    }else{
        [super forwardInvocation:anInvocation];
    }
}

解释:在没有动态添加方法情况下,第(2)(3)步,都是将处理消息的操作,转移给了其他的对象。区别就是在第(2)步转发,只能转发一个指定的对象;在第(3)步的话,可以转发多个对象。

 

好玩的东西:利用oc消息转发,实现多重delegate代理;

大家都比较清楚,在通常情况下,delegate只能对象之间是一对一通信的,通过上述的消息转发的分析,在转发的第(3)步,可以实现多重代理,即多个委托对象。我先上一段代码,后面再解读代码逻辑;

(1)多重代理的处理类,保存多个委托对象,通过消息转发将要执行委托函数转发至其他委托对象(记住本类不实现任何委托相关的逻辑);

#import "MultipleDelegateProxy.h"

@interface MultipleDelegateProxy ()

@property (nonatomic,strong) NSPointerArray *weakRefTargets;

@end

@implementation MultipleDelegateProxy

- (void)setDelegateTargets:(NSArray *)delegateTargets {
    self.weakRefTargets = [NSPointerArray weakObjectsPointerArray];
    for (id delegate in delegateTargets) {
        [self.weakRefTargets addPointer:(__bridge void *)delegate];
    }
}

- (BOOL)respondsToSelector:(SEL)aSelector{
    
    if ([super respondsToSelector:aSelector]) {
        return YES;
    }
    for (id target in self.weakRefTargets) {
        if ([target respondsToSelector:aSelector]) {
            return YES;
        }
    }
    
    return NO;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSMethodSignature *sig = [super methodSignatureForSelector:aSelector];
    if (!sig) {
        for (id target in self.weakRefTargets) {
            if ((sig = [target methodSignatureForSelector:aSelector])) {
                break;
            }
        }
    }
    
    return sig;
}

//转发方法调用给所有delegate
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    for (id target in self.weakRefTargets) {
        if ([target respondsToSelector:anInvocation.selector]) {
            [anInvocation invokeWithTarget:target];
        }
    }
}
@end

(2)使用MultipleDelegateProxy,multipleDelegate作为scrollview的委托对象,multipleDelegate的delegateTargets添加了两个委托对象self和scrollDelegate,在scrollview滚动的过程中会通过MultipleDelegateProxy类中重写的- (BOOL)respondsToSelector:(SEL)aSelector预先判断multipleDelegate是否实现了相应的委托方法(这里肯定要返回yes的,不然就不会出现消息转发),找不到委托方法,最后将消息转发至self和scrollDelegate进行处理,实现多重委托。

@interface MemberCenterViewController () <UIScrollViewDelegate> {
    
    MultipleDelegateProxy *multipleDelegate;
    ScrollDelegate *scrollDelegate;
}

@property (nonatomic, strong) UIScrollView *scroll;

@end

@implementation MemberCenterViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    
    self.scroll = [[UIScrollView alloc] initWithFrame:self.view.bounds];
    self.scroll.backgroundColor = [UIColor lightGrayColor];
    
    self.scroll.contentSize = CGSizeMake(375, 800);
    
    multipleDelegate = [MultipleDelegateProxy new];
    scrollDelegate = [ScrollDelegate new];
    
    multipleDelegate.delegateTargets = @[self,scrollDelegate];
    _scroll.delegate = multipleDelegate;
    
    [self.view addSubview:_scroll];
}


- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    
    NSLog(@"%@",self);
}
@end

在这里我解释一下为什么要重写MultipleDelegateProxy类中的- (BOOL)respondsToSelector:(SEL)aSelector方法?原因很简单,在设置scrollview的delegate属性后,会判断delegate是否实现了相应的委托方法,若没有实现的话,是不会执行这个委托方法(可选的)的;所以我们需要重写,逻辑是通过真正会执行委托的两个委托对象去判断是否实现的委托方法。

 

总结

通过消息转发机制,你可以看到oc这门语言的独特之处,增加了很多灵活性。可以使用它来做一些特殊值的判断处理,比如null值等,减少我们在接收后台返回数据时返回null对我们处理逻辑的影响。当然消息转发的作用不止如此,更多好玩的东西,等待着我们去发掘。

© 著作权归作者所有

共有 人打赏支持
麦兜卖鱼丸
粉丝 12
博文 69
码字总数 69333
作品 0
桂林
iOS工程师
iOS底层原理总结 - 探寻Runtime本质(三)

方法调用的本质 本文我们探寻方法调用的本质,首先通过一段代码,将方法调用代码转为c++代码查看方法调用的本质是什么样的。 通过上述源码可以看出c++底层代码中方法调用其实都是转化为 函数...

xx_cc
07/02
0
0
最清晰的ios消息推送机制教程

研究了一下Apple Push Notification Service,实现的很简单,很环保.原理如下 财大气粗的苹果提供了一堆服务器,每个ios设备和这些服务器保持了一个长连接,ios版本更新提示,手机时钟校准什么的都...

whj
2014/03/16
0
0
软件架构杂谈(三) --- APNS

浅谈软件架构(三) —— APNS ----cnyinlinux 本文将讨论的是常见软件架构中的三角关系——APNS。 APNS是Apple Push Notification Service的简称,即苹果推送通知服务。它用在当前很火热的苹果...

cnyinlinux
2015/06/09
0
0
iOS架构:Proxy实现局部模块化(附Demo)

博客更新记录:首先感谢 Casa Taloyum 前辈对该篇博客错误的指出(或者说打脸😭哈哈),笔者对 AOP 理解错误影响到各位读者的阅读体验,在此深表歉意。Casa Taloyum 前辈指出该玩儿法应该叫...

indulge_in
03/30
0
0
iOS实现本地通知(local notification)的简单示例

本地通知,local notification,用于基于时间行为的通知,比如有关日历或者todo列表的小应用。另外,应用如果在后台执行,iOS允许它在受限的时间内运行,它也会发现本地通知有用。比如,一个...

木木情深
2014/02/27
0
0
iOS 终于支持了 PWA,一起来认识一下这个强化版「小程序」 | 科普

iOS 终于支持了 PWA,一起来认识一下这个强化版「小程序」 | 科普 发布于 2018-04-13 文章被以下专栏收录

少数派
07/06
0
0
PhoneGap使用PushPlugin插件实现消息推送

概括 Android设备通过GCM服务来接收推送消息,而iOS设备这从苹果的APN服务获取。虽然按照负载来说这是两种不同的服务,但是证书都在从第三方接收消息的时候是需要作为一个存储和转发类型的服...

Mr-iu
2014/04/04
0
0
iOS开发系列--通知与消息机制

概述 在多数移动应用中任何时候都只能有一个应用程序处于活跃状态,如果其他应用此刻发生了一些用户感兴趣的那么通过通知机制就可以告诉用户此时发生的事情。iOS中通知机制又叫消息机制,其包...

TomatosX
2015/03/19
0
0
iPhone接收Taiwan旗帜表情时奔溃——远程IOS bug?

      漏洞详情:   Emoji适用于:iPhone 5s及之后机型,iPad Air及之后机型,iPod touch 6代;   影响:在特定配置情况下处理表情时会导致DoS;   描述:DoS问题CVE编号:CVE-201...

嘶吼RoarTalk
07/14
0
0
fir.im Weekly - 每个程序员都应当拥有的技能树

本周收集了一些优秀的 iOS & Android 开发资源和程序员 IT 技能拓展的 Tips. 知道创宇研发技能表 v3.0 作为程序员可能都听说过【知道创宇】,他们是一家黑客文化浓厚的安全公司。这张技能表为...

风起云飞fir_im
2015/09/06
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

庆祝自己过了ACP!!

经过三个月的努力,ACP终于过了,但这只是敏捷实践的开始,路还长

talen
4分钟前
0
0
markdown

首次使用Markdown写博客,好激动! Hexo在Github中搭建博客系统(5)Markdown语法 Hexo在Github中搭建博客系统(5)Markdown语法

miaojiangmin
5分钟前
0
0
10.34 linux系统日志 10.35 screen工具

liunx系统日志 内容: • /var/log/messages • /etc/logrotate.conf 日志切割配置文件 参考https://my.oschina.net/u/2000675/blog/908189 • dmesg命令 • /var/log/dmesg 日志 • last命令......

Linux_老吴
5分钟前
0
0
java 重写排序规则,用于代码层级排序

1.dataList 是个List<Map<String,Object>> 类型的数据,所以比较的时候是冲map中获取数据,并且数据不能为空。 2.dataList 类型是由自己定义的,new Comparator<Map<String,Object>> 也是对应......

轻量级赤影
12分钟前
0
0
分布式大型互联网企业架构!

摘要: 开发工具 1.Eclipse IDE:采用Maven项目管理,模块化。 2.代码生成:通过界面方式简单配置,自动生成相应代码,目前包括三种生成方式(增删改查):单表、一对多、树结构。生成后的代码...

明理萝
12分钟前
0
1
对MFC程序的一点逆向分析:定位按钮响应函数的办法

因为消息响应函数保存在AFX_MSGMAP_ENTRY数组中, 观察nMessage、nCode、nID、pfn利用IDA在rdata段中搜索即可, 在IDA中找到代码段基址0x401000,函数地址0x403140, 在WinDbg中运行!addre...

oready
12分钟前
0
0
阻抗匹配与史密斯(Smith)圆图基本原理

参考:http://bbs.eeworld.com.cn/thread-650695-1-1.html

whoisliang
18分钟前
0
0
maven配置文件分离

一、 简介 遇到很多次别人处理的项目,测试环境,本地开发和线上环境的配置不一样,每一次部署都要重新修改配置文件,提交审核代码,才能打包,非常不方便。 其实相信很多人都知道可以使用m...

trayvon
18分钟前
0
0
MacOS和Linux内核的区别

导读 有些人可能认为MacOS和Linux内核有相似之处,因为它们可以处理类似的命令和类似的软件。甚至有人认为苹果的MacOS是基于linux的。事实上,这两个内核的历史和特性是非常不同的。今天,我...

问题终结者
34分钟前
1
0
SpringBoot | 第八章:统一异常、数据校验处理

前言 在web应用中,请求处理时,出现异常是非常常见的。所以当应用出现各类异常时,进行异常的捕获或者二次处理(比如sql异常正常是不能外抛)是非常必要的,比如在开发对外api服务时,约定了响...

oKong
42分钟前
3
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部