響應(yīng)鏈UIResponder事件的交互學(xué)習(xí)

參考了:https://casatwy.com/responder_chain_communication.html
參考了:https://github.com/qingfengiOS/MutableCellTableView

一般對(duì)象之間事件監(jiān)聽我們可以用代理,比如這種情況,控制器的子view(CustomView),上有一個(gè)btn點(diǎn)擊事件,一般這種事件監(jiān)聽?wèi)?yīng)該交由控制器統(tǒng)一處理

@protocol CustomViewDelegate <NSObject>
- (void)btnClick:(UIButton *)btn;
@end

@interface CustomView : UIView
@property (nonatomic, weak) id<CustomViewDelegate> delegate;
@end

- (IBAction)btnClick:(UIButton *)sender {
    if ([_delegate respondsToSelector:@selector(btnClick:)]) {
        [_delegate btnClick:sender];
    }
}

在控制器中遵守協(xié)議,控制器作為代理,然后實(shí)現(xiàn)代理方法

// 在控制器中遵守協(xié)議
@interface ViewController ()<CustomViewDelegate>
@end
- (void)viewDidLoad {
    [super viewDidLoad];
    
    CustomView *customV = [[NSBundle mainBundle] loadNibNamed:@"CustomView" owner:self options:nil].firstObject;
    customV.frame = CGRectMake(10, 100, [UIScreen mainScreen].bounds.size.width-10, 250);
    [self.view addSubview:customV];
    // 控制器作為代理
    customV.delegate = self;
}

// 實(shí)現(xiàn)代理方法
- (void)btnClick:(nonnull UIButton *)btn {
    NSLog(@"%s,parameter=%@",__func__,btn);
}

然而,發(fā)現(xiàn)一個(gè)新的思路:使用UIResponder實(shí)現(xiàn)事件響應(yīng)鏈條傳遞事件,凡是繼承UIResponder的控件都可以比如UIView,我們知道大部分控件都是直接或者間接繼承自UIView,比如UIButton,UILabel,UIImageView,UIControl,UISwitch,UIScrollView,UITextView,UITextField,UISearchBar,UIPickerView等等

1.給UIResponder擴(kuò)展分類

@interface UIResponder (Router)

/**
 凡是繼承UIResponder的控件,都可以通過事件響應(yīng)鏈條傳遞事件

 @param eventName 事件名
 @param userInfo 附加參數(shù),nullable可以為空
 */
- (void)routerEventWithName:(NSString *)eventName userInfo:(nullable NSDictionary *)userInfo;


/**
 通過方法SEL生成NSInvocation

 @param selector 方法
 @return Invocation對(duì)象
 */
- (NSInvocation *)createInvocationWithSelector:(SEL)selector;

@end

實(shí)現(xiàn)

@implementation UIResponder (Router)

- (void)routerEventWithName:(NSString *)eventName userInfo:(nullable NSDictionary *)userInfo{
    
    NSLog(@"UIResponder---eventName=%@,userInfo=%@",eventName,userInfo);
    [[self nextResponder] routerEventWithName:eventName userInfo:userInfo];
}

- (NSInvocation *)createInvocationWithSelector:(SEL)selector{
    
    // 通過方法名創(chuàng)建方法簽名 注意:不能使用 [[NSInvocation alloc] init]也不可以用下面這個(gè)方法
   // NSMethodSignature *signature = [NSInvocation instanceMethodSignatureForSelector:selector];

    NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:selector];
    // 通過方法簽名創(chuàng)建invocation
    // signature == nil 這里就會(huì)崩潰
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    [invocation setTarget:self];
    [invocation setSelector:selector];
    return invocation;
}

2.發(fā)送事件(子view中)

#import "UIResponder+Router.h"

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

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

3.響應(yīng)處理事件(以控制器為示例)

#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];
    // 如果有需求 可以把響應(yīng)鏈繼續(xù)傳遞下去 和super 一樣效果
    [[self nextResponder] routerEventWithName:eventName userInfo:userInfo];
    // [super routerEventWithName:eventName userInfo:userInfo];
}

// 處理事件
- (void)handleEventWithName:(NSString *)eventName parameter:(NSDictionary *)parameter{
    
    // 獲取invocation對(duì)象
    NSInvocation *invocation = self.eventStrategyDictionary[eventName];

    // 設(shè)置invocation參數(shù)
    // 因?yàn)橛袃蓚€(gè)隱藏參數(shù)self和_cmd,所有index從2開始
    [invocation setArgument:&parameter atIndex:2];

    // 調(diào)用方法
    [invocation invoke];
}

Strategy模式進(jìn)行更好的事件處理


/// 事件策略字典 key:事件名 value:事件的invocation對(duì)象
@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;
}

// 處理button點(diǎn)擊事件
- (void)customViewButtonClickWithParameter:(NSDictionary *)parameter{
    
    NSLog(@"%s,parameter=%@",__func__,parameter);
}

使用Strategy模式,即可避免多事件處理場景下導(dǎo)致的冗長if-else語句

對(duì)響應(yīng)鏈的調(diào)用打印

UIResponder---eventName=CustomSwitchEvent,userInfo={
mySwitchKey = 0;
}
UIResponder---eventName=CustomSwitchEvent,userInfo={
mySwitchKey = 0;
}
controller---eventName=CustomSwitchEvent,userInfo={
mySwitchKey = 0;
}
[ViewController customViewSwitchValueChangeWithParameter:],parameter={
mySwitchKey = 0;
}
UIResponder---eventName=CustomSwitchEvent,userInfo={
mySwitchKey = 0;
}
UIResponder---eventName=CustomSwitchEvent,userInfo={
mySwitchKey = 0;
}
AppDelegate---eventName=CustomSwitchEvent,userInfo={
mySwitchKey = 0;
}

由于響應(yīng)鏈?zhǔn)且粚右粚油蟼鬟f的,最終會(huì)經(jīng)過AppDelegate,所以如果要對(duì)所有事件做統(tǒng)一處理可以在AppDelegate這里

#import "UIResponder+Router.h"

@interface AppDelegate ()

@end

@implementation AppDelegate

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

在整個(gè)響應(yīng)鏈中,任何響應(yīng)的地方都可以處理,比如添加一些參數(shù)(可使用Decorator模式,能夠更加有序地收集、歸攏數(shù)據(jù),降低了工程的維護(hù)成本),處理一些業(yè)務(wù)邏輯

如果頁面比較復(fù)雜,比如商品詳情頁,業(yè)務(wù)邏輯非常多,我們可以新建一個(gè)文件(EventProxy),把所有的響應(yīng)事件放到這個(gè)文件進(jìn)行統(tǒng)一處理,這樣就達(dá)到了為控制器減負(fù),看起來有點(diǎn)像新的架構(gòu)

新的架構(gòu)模式:MVCE(Modle View Controller Event)

EventProxy.h的聲明

@interface EventProxy : NSObject

- (void)handleEventWithName:(NSString *)eventName parameter:(NSDictionary *)parameter;

@end

EventProxy.m的實(shí)現(xiàn)

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

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

@interface EventProxy ()

// 使用Strategy模式,即可避免多事件處理場景下導(dǎo)致的冗長if-else語句
/// 事件策略字典 key:事件名 value:事件的invocation對(duì)象
@property (strong, nonatomic) NSDictionary *eventStrategyDictionary;

@end

@implementation EventProxy

- (NSInvocation *)createInvocationWithSelector:(SEL)selector{
    
    // 通過方法名創(chuàng)建方法簽名 注意:不能使用 [[NSInvocation alloc] init]也不可以用下面這個(gè)方法
    // NSMethodSignature *signature = [NSInvocation instanceMethodSignatureForSelector:selector];
    
    NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:selector];
    // 通過方法簽名創(chuàng)建invocation
    // signature == nil 這里就會(huì)崩潰
    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對(duì)象
    NSInvocation *invocation = self.eventStrategyDictionary[eventName];
    
    // 設(shè)置invocation參數(shù)
    // 因?yàn)橛袃蓚€(gè)隱藏參數(shù)self和_cmd,所有index從2開始
    [invocation setArgument:&parameter atIndex:2];
    
    // 調(diào)用方法
    [invocation invoke];
}

- (void)customViewButtonClickWithParameter:(NSDictionary *)parameter{
    
    NSLog(@"具體方法實(shí)現(xiàn),parameter=%@",parameter);
}
- (void)customViewSwitchValueChangeWithParameter:(NSDictionary *)parameter{
    
    NSLog(@"具體方法實(shí)現(xiàn),parameter=%@",parameter);
}
- (void)customViewImageViewTapWithParameter:(NSDictionary *)parameter{
    
    NSLog(@"具體方法實(shí)現(xiàn),parameter=%@",parameter);
}

控制器中的代碼實(shí)現(xiàn)

@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];
    // 把響應(yīng)鏈繼續(xù)傳遞下去 和super 一樣效果
    [[self nextResponder] routerEventWithName:eventName userInfo:userInfo];
    // [super routerEventWithName:eventName userInfo:userInfo];
}
@end

這樣控制器把事件的處理交給eventProxy去處理,這樣控制器是不是很清爽呢,是不是很像MVCE(Modle View Controller Event)

總結(jié)一下:

  • 1.在子view中發(fā)送事件
  • 2.一般在控制器中處理事件,也可以發(fā)送給一個(gè)專門對(duì)象eventProxy進(jìn)行處理這個(gè)事件
  • 3.如果有需要可以繼續(xù)傳給其他地方進(jìn)行處理,比如父控件,AppDelegate

demo下載

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容