IOS - 基于ResponderChain的对象交互方式

文章链接:https://juejin.cn/post/6881552856202084360
一般我们写交互,无非几种方式:Delegate、KVO、Block、Protocol、多态、Target-Action,且一般在Controller中处理,但这么写呢,可能导致一个问题,就是逻辑代码比较分散,不够集中,那么有没有一种方式能够集中的处理不同对象间的交互?这里有一个思路

我们都知道,凡继承UIResponder的控件,都会通过事件响应链条传递事件。如:UIView UIButton,UILabel,UIImageView,UIControl,UISwitch,UIScrollView,UITextView,UITextField,UISearchBar,UIPickerView我们可以利用这个特性,给UIResponder扩展分类。
##拿捏:
1、UIResponder扩展
.h中


/**
 @param eventName 事件名
 @param userInfo 附加参数,nullable可以为空
 */

- (void)routerEventWithName:(NSString *)eventName userInfo:(nullable NSDictionary *)userInfo;

/**
 通过方法SEL生成NSInvocation

 @param selector 方法
 @return Invocation对象
 */

- (NSInvocation *)createInvocationWithSelector:(SEL)selector;

@end

.m中


- (void)routerEventWithName:(NSString *)eventName userInfo:(nullable NSDictionary *)userInfo{

  NSLog(@"UIResponder---eventName=%@,userInfo=%@",eventName,userInfo);
  [[self nextResponder] routerEventWithName:eventName userInfo:userInfo];
  }

- (NSInvocation *)createInvocationWithSelector:(SEL)selector{

 // 通过方法名创建方法签名 注意:不能使用 [[NSInvocation alloc] init]也不可以用下面这个方法
 // NSMethodSignature *signature = [NSInvocation instanceMethodSignatureForSelector:selector];

   NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:selector];
   // 通过方法签名创建invocation
   // signature == nil 这里就会崩溃
   NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
   [invocation setTarget:self];
   [invocation setSelector:selector];
   return invocation;
}

2、在子view中,发送事件

#import "UIResponder+Router.h"

- (IBAction)btnClick:(UIButton *)sender {

    [self routerEventWithName:kEventMyButtonName userInfo: @{@"btnKey": sender.currentTitle}];
}

3、在Controller响应处理事件

#import "UIResponder+Router.h"

#pragma mark - Event Response
- (void)routerEventWithName:(NSString *)eventName userInfo:(NSDictionary *)userInfo{
    
    NSLog(@"controller---eventName=%@,userInfo=%@",eventName,userInfo);
    // 处理事件
    [self handleEventWithName:eventName parameter:userInfo];
    // 如果有需求 可以把响应链继续传递下去 和super 一样效果
    [[self nextResponder] routerEventWithName:eventName userInfo:userInfo];
    // [super routerEventWithName:eventName userInfo:userInfo];
}

// 处理事件
- (void)handleEventWithName:(NSString *)eventName parameter:(NSDictionary *)parameter{
    
    // 获取invocation对象
    NSInvocation *invocation = self.eventStrategyDictionary[eventName];

    // 设置invocation参数
    // 因为有两个隐藏参数self和_cmd,所有index从2开始
    [invocation setArgument:&parameter atIndex:2];

    // 调用方法
    [invocation invoke];
}

4、结合Strategy(策略)模式,更好的处理事件。

/// 策略字典 key:事件名 value:事件的invocation对象
@property (strong, nonatomic) NSDictionary *eventStrategyDictionary;

#pragma mark - Getter
- (NSDictionary <NSString *, NSInvocation *>*)eventStrategyDictionary {
  
    if (!_eventStrategyDictionary) {
      
        NSInvocation *btnInvocation = [self createInvocationWithSelector:@selector(customViewButtonClickWithParameter:)];
        NSInvocation *switchInvocation = [self createInvocationWithSelector:@selector(customViewSwitchValueChangeWithParameter:)];
        NSInvocation *imgInvocation = [self createInvocationWithSelector:@selector(customViewImageViewTapWithParameter:)];
        
        _eventStrategyDictionary = @{ kEventMyButtonName: btnInvocation,
                                      kEventMySwitchName: switchInvocation,
                                      kEventMyImageViewName: imgInvocation
                                    };
    }
    return _eventStrategyDictionary;
}

/// 具体事件
- (void)customViewButtonClickWithParameter:(NSDictionary *)parameter{
    NSLog(@"按钮 具体方法实现,parameter=%@",parameter);
}

- (void)customViewSwitchValueChangeWithParameter:(NSDictionary *)parameter{
    NSLog(@"开关 具体方法实现,parameter=%@",parameter);
}

- (void)customViewImageViewTapWithParameter:(NSDictionary *)parameter{
    NSLog(@"图片 具体方法实现,parameter=%@",parameter);
}

由于响应链是一层一层往上传递的,最终会到达AppDelegate,所以若要对所有事件做统一处理可以在AppDelegate这里:

#import "UIResponder+Router.h"

@interface AppDelegate ()

@end

@implementation AppDelegate

- (void)routerEventWithName:(NSString *)eventName userInfo:(NSDictionary *)userInfo{
    NSLog(@"AppDelegate---eventName=%@,userInfo=%@",eventName,userInfo);
}
@end

这里有一个iOS交流圈有兴趣的可以了解下:891 488 181 分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流讨论!
到此,简单的利用ResponderChain传递事件的特性而实现的集中处理交互,就差不多了,但还能再进一步:如果页面比较复杂,业务逻辑非常多,我们可以新建一个文件(EventProxy),把所有的响应事件放到这个文件进行统一处理,这样就达到了为控制器减负和集中处理逻辑便于维护的目的,也就是:MVCE(Modle View Controller Event)(类似于MVC,E 的作用在于给C减负)

拿捏:

##MVCE(Modle View Controller Event)
1、EventProxy
.h

@interface EventProxy : NSObject
- (void)handleEventWithName:(NSString *)eventName parameter:(NSDictionary *)parameter;
@end

.m

#import "EventProxy.h"
#import "UIResponder+Router.h"

NSString *const kEventMyButtonName = @"CustomButtonEvent";
NSString *const kEventMySwitchName = @"CustomSwitchEvent";
NSString *const kEventMyImageViewName = @"CustomImageViewEvent";

@interface EventProxy ()

// 使用Strategy模式,即可避免多事件处理场景下导致的冗长if-else语句
/// 事件策略字典 key:事件名 value:事件的invocation对象
@property (strong, nonatomic) NSDictionary *eventStrategyDictionary;

@end

@implementation EventProxy

- (NSInvocation *)createInvocationWithSelector:(SEL)selector{
    
    // 通过方法名创建方法签名 注意:不能使用 [[NSInvocation alloc] init]也不可以用下面这个方法
    // NSMethodSignature *signature = [NSInvocation instanceMethodSignatureForSelector:selector];
    
    NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:selector];
    // 通过方法签名创建invocation
    // signature == nil 这里就会崩溃
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    [invocation setTarget:self];
    [invocation setSelector:selector];
    return invocation;
}

#pragma mark - Getter
- (NSDictionary <NSString *, NSInvocation *>*)eventStrategyDictionary {
  
    if (!_eventStrategyDictionary) {
      
        NSInvocation *btnInvocation = [self createInvocationWithSelector:@selector(customViewButtonClickWithParameter:)];
        NSInvocation *switchInvocation = [self createInvocationWithSelector:@selector(customViewSwitchValueChangeWithParameter:)];
        NSInvocation *imgInvocation = [self createInvocationWithSelector:@selector(customViewImageViewTapWithParameter:)];
        
        _eventStrategyDictionary = @{ kEventMyButtonName: btnInvocation,
                                      kEventMySwitchName: switchInvocation,
                                      kEventMyImageViewName: imgInvocation
                                      };
        
    }
    return _eventStrategyDictionary;
}

- (void)handleEventWithName:(NSString *)eventName parameter:(NSDictionary *)parameter{
    
    // 获取invocation对象
    NSInvocation *invocation = self.eventStrategyDictionary[eventName];
    
    // 设置invocation参数
    // 因为有两个隐藏参数self和_cmd,所有index从2开始
    [invocation setArgument:&parameter atIndex:2];
    
    // 调用方法
    [invocation invoke];
}

- (void)customViewButtonClickWithParameter:(NSDictionary *)parameter{
    NSLog(@"具体方法实现,parameter=%@",parameter);
}

- (void)customViewSwitchValueChangeWithParameter:(NSDictionary *)parameter{    
    NSLog(@"具体方法实现,parameter=%@",parameter);
}

- (void)customViewImageViewTapWithParameter:(NSDictionary *)parameter{
    NSLog(@"具体方法实现,parameter=%@",parameter);
}

2、Controller中

@interface ViewController ()
@property (strong, nonatomic) EventProxy *eventProxy;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // 初始化事件代理
    self.eventProxy = [[EventProxy alloc] init];
}

#pragma mark - Event Response
- (void)routerEventWithName:(NSString *)eventName userInfo:(NSDictionary *)userInfo{
    
    NSLog(@"controller---eventName=%@,userInfo=%@",eventName,userInfo);
    // 处理事件
    [self.eventProxy handleEventWithName:eventName parameter:userInfo];
  
    // 如果想在做统一处理,把响应链继续传递下去,最终会到Appdelegate,否则,就此打住
    [[self nextResponder] routerEventWithName:eventName userInfo:userInfo];
    // =  [super routerEventWithName:eventName userInfo:userInfo];
}
@end

##总结:

1.在子view中发送事件。
2.一般在控制器中处理事件,逻辑较为复杂的情况,也可以发送给一个专门对象eventProxy进行处理这个事件。
3.如果有需要可以继续传给父控件,AppDelegate处理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值