Runtime系列4--Message Forwarding

消息转发原理

简单来说,就是在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的代理事件做处理,保证他们不会冲突和覆盖。

分析

如果按照传统的做法,我们把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下载地址

comments powered by Disqus