參考了: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:¶meter 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:¶meter 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