文档章节

iOS-消息转发机制

麦兜卖鱼丸
 麦兜卖鱼丸
发布于 2016/03/27 14:45
字数 1417
阅读 78
收藏 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
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与JS交互之WKWebView-WKScriptMessageHandler协议

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

QiShare
09/02
0
0
一分钟越狱 iOS 11.4|世界顶级黑客大会 DEFCON 议题详解

雷锋网编者按:苹果公司在 macOS 和 iOS 中都采用了沙盒机制保护系统不受恶意软件的攻击。在世界著名的黑客大会 DEFCON 的这次演讲中,来自阿里安全的安全研究员分析了最新版的 iOS 中的沙盒...

李勤
08/13
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Oracle return exit continue

常在循环体中看到下面3种语句: return exit continue 举例说明 啥都没有 -- none begin for i in 1 .. 10 loop if i < 5 then dbms_output.put_line('i < 5, i = ' || to_char......

taadis
58分钟前
2
0
JSONObject 转换时出错 InvocationTargetException

JSONObject 转换时出错java.lang.reflect.InvocationTargetException 一时看不出来是什么问题。 挺奇怪的。 百度参考了一下这个 网页的解决方案 说是类型不对,空? 仔细查看代码,果然是有一...

之渊
今天
3
0
no such module 'pop'问题

在github上 clone 了一个 swift 项目,编译时提示"no such module 'POP'"错误,查了一下居然是因为podfile中指定的最低版本是iOS 11.0,大于我测试手机的iOS版本10.3.3,将Podfile中的最低版...

yoyoso
今天
3
0
redis 系列一 -- 简介及安装

1.简介 redis -- remote dictionary server 远程字典服务 使用 C 语言编写; 高性能的 key-value数据库; 内存数据库,支持数据持久化。 Redis 是一个开源(BSD许可)的,内存中的数据结构存...

imbiao
今天
4
0
nginx log记录请求响应时间

有时为了方便分析接口性能等,需要记录请求的时长,通过修改nginx的日志格式可以做到,如 添加一个新的log_format log_format timed_combined '$remote_addr - $remote_user [$time_local] "...

swingcoder
今天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部