Runtime系列4--消息转发

作者: 西木柚子 | 来源:发表于2016-08-12 13:47 被阅读153次

    消息转发原理

    简单来说,就是在Objective-C中,使用对象进行方法调用是一个消息发送的过程(Objective-C采用“动态绑定机制”,所以所要调用的方法直到运行期才能确定)。

    方法在调用时,系统会查看这个对象能否接收这个消息(查看这个类有没有这个方法,或者有没有实现这个方法。),如果不能并且只在不能的情况下,就会调用下面这几个方法,给你“补救”的机会,你可以先理解为几套防止程序crash的备选方案,我们就是利用这几个方案进行消息转发,注意一点,前一套方案实现后,后一套方法就不会执行。如果这几套方案你都没有做处理,那么程序就会报错crash

    方案一:
    + (BOOL)resolveInstanceMethod:(SEL)sel
    + (BOOL)resolveClassMethod:(SEL)sel
    
    
    方案二:
    - (id)forwardingTargetForSelector:(SEL)aSelector
    
    
    方案三:
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
    - (void)forwardInvocation:(NSInvocation *)anInvocation;
    

    具体的看下面这篇文章就可以了:

    http://my.oschina.net/Jacedy/blog/625343


    消息转发使用场景

    这是本文的重点,主要讲下消息转发机制在实际中的使用场景,发现网上关于这方面的讲解还是比较少。

    需求:

    我们自定义了一个SGScrollview,继承自系统的UITableView,然后把SGScrollview添加到ViewController上面。要求在SGScrollview内部监听scrollview的代理事件做一些处理,同时在viewcontroller上也要监听scrollview的代理事件做处理,保证他们不会冲突和覆盖。

    分析

    如果按照传统的方法,我们会如下做

    
    viewController.m
    ====================
    
    -(void)viewDidLoad{
        [super viewDidLoad];
        SGScrollview *SV = [[SGScrollview alloc]initWithFrame:self.view.bounds];
        SV.dataSource = self;
        SV.delegate =self;
        [self.view addSubview:SV];
    }
    
    -(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
        
        NSLog(@"%s的方法被调用",__PRETTY_FUNCTION__);
    }
    
    
    

    这个时候我们把SGScrollview的代理设置为viewcontroller,那么这个时候scrollViewWillBeginDragging方法只会被viewcontroller监听到,而在SGScrollview内部的scrollViewWillBeginDragging方法就失效了。

    反之如果在SGScrollview设置代理为自己,那么viewcontroller的代理方法监听也会失效。

    我们无法同时在两个不同的类里面监听同一个父类(UIScrollview)的代理方法,为了解决这个问题,我们可以用runtime的消息转发机制,建立一个代理转发类,让它来负责把代理方法的消息发送给不同的消息接受者。

    下面直接上代码

    #import <Foundation/Foundation.h>
    
    @interface MessageInterceptor : NSObject
    @property (nonatomic, assign) id receiver;
    @property (nonatomic, assign) id middleMan;
    @end
    
    ====================================
    #import "MessageInterceptor.h"
    
    @implementation MessageInterceptor
    - (id)forwardingTargetForSelector:(SEL)aSelector {
        if ([self.middleMan respondsToSelector:aSelector]) {
            return self.middleMan;
        }
        
        if ([self.receiver respondsToSelector:aSelector]) {
            return self.receiver;
        }
        
        return [super forwardingTargetForSelector:aSelector];
    }
    
    - (BOOL)respondsToSelector:(SEL)aSelector {
        if ([self.middleMan respondsToSelector:aSelector])
        {
            return YES;
        }
        
        if ([self.receiver respondsToSelector:aSelector])
        {
            return YES;
        }
        
        return [super respondsToSelector:aSelector];
    }
    @end
    
    
    
    
    #import <UIKit/UIKit.h>
    #import "MessageInterceptor.h"
    
    @interface SGScrollview : UITableView<UITableViewDelegate>
    @property(nonatomic,strong)MessageInterceptor *delegate_interceptor;
    @end
    ===================
    
    #import "SGScrollview.h"
    
    @implementation SGScrollview
    -(instancetype)initWithFrame:(CGRect)frame{
        if (self == [super initWithFrame:frame]) {
            self.delegate_interceptor = [[MessageInterceptor alloc] init];
            self.delegate_interceptor.middleMan = self;
            [super setDelegate:(id)self.delegate_interceptor];    }
        return self;
    }
    
    -(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
        NSLog(@"%s的方法被调用",__PRETTY_FUNCTION__);
    
        if ([self.delegate respondsToSelector:@selector(scrollViewWillBeginDragging:)]) {
            [self.delegate scrollViewWillBeginDragging:scrollView];
        }
    }
    
    
    - (id)delegate
    {
        return self.delegate_interceptor.receiver;
    }
    
    - (void)setDelegate:(id)newDelegate {
        [super setDelegate:nil];
        self.delegate_interceptor.receiver = newDelegate;
        [super setDelegate:(id)self.delegate_interceptor];
    }
    
    
    @end
    
    
    
    #import "ViewController.h"
    #import <objc/message.h>
    #import "SGScrollview.h"
    
    @interface ViewController ()<UIScrollViewDelegate,UITableViewDataSource,UITableViewDelegate>
    
    @end
    
    @implementation ViewController
    -(void)viewDidLoad{
        [super viewDidLoad];
        SGScrollview *SV = [[SGScrollview alloc]initWithFrame:self.view.bounds];
        SV.dataSource = self;
        SV.delegate =self;
        [self.view addSubview:SV];
         [SV registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"];
    }
    
    
    
    -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
        return 20;
    }
    
    -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
        cell.textLabel.text = [NSString stringWithFormat:@"%zd",indexPath.row];
        return cell;
    }
    
    -(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
        
        NSLog(@"%s的方法被调用",__PRETTY_FUNCTION__);
    }
    
    
    
    @end
    
    

    此时拖动tableview,输出如下:

    
    2016-08-12 13:31:09.579 test1[4474:410866] -[SGScrollview scrollViewWillBeginDragging:]的方法被调用
    2016-08-12 13:31:11.630 test1[4474:410866] -[ViewController scrollViewWillBeginDragging:]的方法被调用
    

    可以看到两个类都可以监听到代理方法。


    总结

    上面这个例子可以扩展到下面这样的场景:

    一个子类继承自一个带有代理方法的父类,需要在另外一个引用了该子类的类里面监听该子类的父类的代理方法,同时在子类本身也要监听这些代理方法,都可以使用上述方法。

    Demo下载地址

    相关文章

      网友评论

        本文标题:Runtime系列4--消息转发

        本文链接:https://www.haomeiwen.com/subject/xsxesttx.html