文档章节

iOS-消息转发机制

麦兜卖鱼丸
 麦兜卖鱼丸
发布于 2016/03/27 14:45
字数 1417
阅读 79
收藏 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对我们处理逻辑的影响。当然消息转发的作用不止如此,更多好玩的东西,等待着我们去发掘。

© 著作权归作者所有

共有 人打赏支持
上一篇: iOS弹跳动画
麦兜卖鱼丸
粉丝 12
博文 69
码字总数 69333
作品 0
桂林
iOS工程师
私信 提问
iOS底层原理总结 - 探寻Runtime本质(三)

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

xx_cc
07/02
0
0
Cocos-BCX 技术周报第十一期

Cocos-BCX 技术周报第十一期 2018-09-05 17:25编辑: suiling分类:区块链来源:CocosBCX 区块链周报CocosBCX 招聘信息: iOS开发 iOS开发 iOS开发 app开发上架H5技术 app开发技术 图像处理及...

suiling
09/05
0
0
最清晰的ios消息推送机制教程

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

whj
2014/03/16
0
0
阿里潘多拉实验室成功越狱 iOS 12 系统

雷锋网(公众号:雷锋网)消息,9月18日凌晨,Apple(苹果)正式对外发布最新的 iOS 12系统,几小时后,其安全防护系统很快就被阿里安全潘多拉实验室安全专家攻破,成为全球首个完成 iOS 12 完...

郭佳
09/19
0
0
iOS与JS交互之WKWebView-WKScriptMessageHandler协议

级别:★★☆☆☆ 标签:「iOS与JS交互」「WKWebView与JS交互」「WKJSMessageHandler」 作者: Xs·H 审校: QiShare团队 先解释下标题:“iOS与JS交互”。iOS指原生代码(文章只有示例),J...

QiShare
09/02
0
0

没有更多内容

加载失败,请刷新页面

加载更多

JeeSite 4.x 树形结构的表设计和用法

有些同仁对于 JeeSite 4 中的树表设计不太了解,本应简单的方法就可实现,却写了很多复杂的语句和代码,所以有了这篇文章。 在 JeeSite 4 中的树表设计我还是相对满意的,这种设计比较容易理...

ThinkGem
14分钟前
1
0
0022-如何永久删除Kafka的Topic

1.问题描述 使用kafka-topics --delete命令删除topic时并没有真正的删除,而是把topic标记为:“marked for deletion”,导致重新创建相同名称的Topic时报错“already exists”。 2.问题复现...

Hadoop实操
17分钟前
0
0
技术工坊|区块链中的p2p网络(上海)

区块链是一个去中心化的系统,每个节点分布在全球各地,那么节点之间是如何自发地组成网络,又如何进行通信的?区块链中的p2p网络算法与bt下载中的p2p网络有什么区别?11月28日,第29期技术工...

HiBlock
22分钟前
1
0
MySQL性能优化之char、varchar、text的区别

在存储字符串时, 可以使用char、varchar或者text类型, 那么具体使用场景呢? 参考下面这个表结构: 1、 char长度固定, 即每条数据占用等长字节空间;适合用在身份证号码、手机号码等定。 ...

hansonwong
25分钟前
0
0
并发编程系列:4大并发工具类的功能、原理、以及应用场景

一:并发工具包 1.并发工具类 提供了比synchronized更加高级的各种同步结构:包括CountDownLatch、CyclicBarrier、Semaphore等,可以实现更加丰富的多线程操作。 2.并发容器 提供各种线程安全...

游人未归
35分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部