内容简介:在iOS开发中,常见的MVC中,复杂界面的Controller中的代码极其臃肿,动则上千行的代码量对后期维护简直是一种灾难,因此MVC也被调侃为Messive ViewController,特别是有多种类型Cell的TableView存在时,在传统iOS的对象间交互模式就那么几种:直接property传值、delegate、KVO、block、protocol、多态、Target-Action。这次来说说基于ResponderChain来实现对象间交互。这种方式通过在UIResponder上挂一个cate
在iOS开发中,常见的MVC中,复杂界面的Controller中的代码极其臃肿,动则上千行的代码量对后期维护简直是一种灾难,因此MVC也被调侃为Messive ViewController,特别是有多种类型Cell的TableView存在时,在 -tableView:cellForRowAtIndexPath:
代理方法中充斥着大量的if-else分支,这次我们尝试用一种新的方式来“优雅”地实现这个方法。
传统iOS的对象间交互模式就那么几种:直接property传值、delegate、KVO、block、protocol、多态、Target-Action。这次来说说基于ResponderChain来实现对象间交互。
这种方式通过在UIResponder上挂一个category,使得事件和参数可以沿着responder chain逐步传递。
这相当于借用responder chain实现了一个自己的事件传递链。这在事件需要层层传递的时候特别好用,然而这种对象交互方式的有效场景仅限于在responder chain上的UIResponder对象上。
二、MVVM分离逻辑,解耦
网上关于MVVM的文章很多而且每个人的理解可能都有小小的差别,这里不做赘述,这里说说我在项目中所用到的MVVM,如果错误,请看官多多指教。我的tableView定义在ViewModel中,其代理方法也在ViewModel实现:
头文件中:
#import <UIKit/UIKit.h> @interface QFViewModel : NSObject /// 暴露一个tableView的属性 提供Controller使用 @property (nonatomic, strong) UITableView *tableView; @end 复制代码
实现文件中两个关键方法:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { id<QFModelProtocol> model = self.dataArray[indexPath.row]; id<QFViewProtocol> cell = [tableView dequeueReusableCellWithIdentifier:model.identifier]; [cell configCellDateByModel:model]; return (UITableViewCell *)cell; } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { id<QFModelProtocol> model = self.dataArray[indexPath.row]; return model.height; } 复制代码
这里用到了协议Protocol做解耦,两个协议,一个视图层的协议 QFViewProtocol
,一个模型的协议 QFModelProtocol
- QFViewProtocol 这里提供一个方法,通过模型数据设置界面的展示
/** 协议用于保存每个cell的数据源设置方法,也可以不用,直接在每个类型的cell头文件中定义,考虑到开放封闭原则,建议使用 */ @protocol QFViewProtocol <NSObject> /** 通过model 配置cell展示 @param model model */ - (void)configCellDateByModel:(id<QFModelProtocol>)model; 复制代码
- QFModelProtocol 这里提供两个属性,一个重用标志符,一个行高
#import <UIKit/UIKit.h> /** 协议用于保存每个model对应cell的重用标志符和行高,也可以不使用这个协议 直接在对一个的model里指明 */ @protocol QFModelProtocol <NSObject> - (NSString *)identifier; - (CGFloat)height; @end 复制代码
在控制器层中直接addSubView:
- (void)initAppreaence { [self.view addSubview:self.viewModel.tableView]; } 复制代码
三、基于ResponderChain传递点击事件
在iOS的事件传递响应中有一棵响应树,使用此可以消除掉各个类中的头文件引用。使用这个特性只需要一个方法即可,为UIResponder添加分类,实现一个方法:
/** 通过事件响应链条传递事件 @param eventName 事件名 @param userInfo 附加参数 */ - (void)routerEventWithName:(NSString *)eventName userInfo:(NSDictionary *)userInfo { [[self nextResponder] routerEventWithName:eventName userInfo:userInfo]; } 复制代码
发送事件的时候使用:
[self routerEventWithName:kEventOneName userInfo:@{@"keyOne": @"valueOne"}]; 复制代码
这里使用了一个字典来做参数的传递,这里可以使用装饰者模式,在事件层层向上传递的时候,每一层都可以往UserInfo这个字典中添加数据。那么到了最终事件处理的时候,就能收集到各层综合得到的数据,从而完成最终的事件处理。
如果要把这个事件继续传递下去直到APPDelegate中的话,调用:
// 把响应链继续传递下去 [super routerEventWithName:eventName userInfo:userInfo]; 复制代码
四、策略模式避免if-else
在《大话设计模式》一书中,使用了商场打折的案例分析了策略模式对于不同算法的封装,有兴趣可以去看看,这里我们使用策略模式封装的是NSInvocation,他用于做方法调用,在消息转发的最后阶段会通过NSInvocation来转发。我们以一个方法调用的实例来看NSInvocation
#pragma mark - invocation调用方法 - (void)invocation { SEL myMethod = @selector(testInvocationWithString:number:); NSMethodSignature *sig = [[self class] instanceMethodSignatureForSelector:myMethod]; NSInvocation * invocatin = [NSInvocation invocationWithMethodSignature:sig]; [invocatin setTarget:self]; [invocatin setSelector:myMethod]; NSString *a = @"string"; NSInteger b = 10; [invocatin setArgument:&a atIndex:2]; [invocatin setArgument:&b atIndex:3]; NSInteger res = 0; [invocatin invoke]; [invocatin getReturnValue:&res]; NSLog(@"%ld",(long)res); } - (NSInteger)testInvocationWithString:(NSString *)str number:(NSInteger)number { return str.length + number; } 复制代码
[invocatin setArgument:&a atIndex:2]; [invocatin invoke];
好了我们回归主题,这里用一个dictionary,保存方法调用的必要参数,字典的key是事件名,value是对应的invocation对象,当事件发生时,直接调用
- (NSDictionary <NSString *, NSInvocation *>*)strategyDictionary { if (!_eventStrategy) { _eventStrategy = @{ kEventOneName:[self createInvocationWithSelector:@selector(cellOneEventWithParamter:)], kEventTwoName:[self createInvocationWithSelector:@selector(cellTwoEventWithParamter:)] }; } return _eventStrategy; } 复制代码
这里调用 UIResponder
中的的方法 - (NSInvocation *)createInvocationWithSelector:(SEL)selector
生成invocation:
/** 通过方法SEL生成NSInvocation @param selector 方法 @return Invocation对象 */ - (NSInvocation *)createInvocationWithSelector:(SEL)selector { //通过方法名创建方法签名 NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:selector]; //创建invocation NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; [invocation setTarget:self]; [invocation setSelector:selector]; return invocation; } 复制代码
五、事件处理
经过上面的步骤,我们可以在Controller中通过 - (void)routerEventWithName:(NSString *)eventName userInfo:(NSDictionary *)userInfo
拿到事件做响应的处理,如果有必要,把这个事件继续传递下去:
#pragma mark - Event Response - (void)routerEventWithName:(NSString *)eventName userInfo:(NSDictionary *)userInfo { // 处理事件 [self handleEventWithName:eventName parameter:userInfo]; // 把响应链继续传递下去 [super routerEventWithName:eventName userInfo:userInfo]; } - (void)handleEventWithName:(NSString *)eventName parameter:(NSDictionary *)parameter { // 获取invocation对象 NSInvocation *invocation = self.strategyDictionary[eventName]; // 设置invocation参数 [invocation setArgument:¶meter atIndex:2]; // 调用方法 [invocation invoke]; } - (void)cellOneEventWithParamter:(NSDictionary *)paramter { NSLog(@"第一种cell事件---------参数:%@",paramter); QFDetailViewController *viewController = [QFDetailViewController new]; viewController.typeName = @"Cell类型一"; viewController.paramterDic = paramter; [self presentViewController:viewController animated:YES completion:nil]; } - (void)cellTwoEventWithParamter:(NSDictionary *)paramter { NSLog(@"第二种cell事件---------参数:%@",paramter); QFDetailViewController *viewController = [QFDetailViewController new]; viewController.typeName = @"Cell类型二"; viewController.paramterDic = paramter; [self presentViewController:viewController animated:YES completion:nil]; } 复制代码
六、后记
本篇到此结束了,总结起来,用到的东西还是不少,很多东西都值得深入:
- Protocol的使用
- 事件处理:事件产生、传递及响应的机制
- 设计模式:策略模式、装饰者模式以及MVVM的使用
- NSInvocation的使用及消息转发机制
有任何意见和建议欢迎交流指导,如果可以,请顺手给个star。
最后,万分感谢Casa大佬的分享!
一种基于ResponderChain的对象交互方式以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- php如何实现session,自己实现session,laravel如何实现session
- AOP如何实现及实现原理
- webpack 实现 HMR 及其实现原理
- Docker实现原理之 - OverlayFS实现原理
- 为什么实现 .NET 的 ICollection 集合时需要实现 SyncRoot 属性?如何正确实现这个属性?
- 自己实现集合框架(十):顺序栈的实现
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
图解TCP/IP : 第5版
[日]竹下隆史、[日]村山公保、[日]荒井透、[日]苅田幸雄 / 乌尼日其其格 / 人民邮电出版社 / 2013-7-1 / 69.00元
这是一本图文并茂的网络管理技术书籍,旨在让广大读者理解TCP/IP的基本知识、掌握TCP/IP的基本技能。 书中讲解了网络基础知识、TCP/IP基础知识、数据链路、IP协议、IP协议相关技术、TCP与UDP、路由协议、应用协议、网络安全等内容,引导读者了解和掌握TCP/IP,营造一个安全的、使用放心的网络环境。 本书适合计算机网络的开发、管理人员阅读,也可作为大专院校相关专业的教学参考......一起来看看 《图解TCP/IP : 第5版》 这本书的介绍吧!