在iOS开发中,常见的MVC中,复杂界面的Controller中的代码极其臃肿,动则上千行的代码量对后期维护简直是一种灾难,所以MVC也被调侃为Messive ViewController,特别是有多种类型Cell的TableView存在时,在-tableView:cellForRowAtIndexPath:
代理方法中充斥着大量的if-else分支,此次咱们尝试用一种新的方式来“优雅”地实现这个方法。html
传统iOS的对象间交互模式就那么几种:直接property传值、delegate、KVO、block、protocol、多态、Target-Action。此次来讲说基于ResponderChain来实现对象间交互。git
这种方式经过在UIResponder上挂一个category,使得事件和参数能够沿着responder chain逐步传递。github
这至关于借用responder chain实现了一个本身的事件传递链。这在事件须要层层传递的时候特别好用,然而这种对象交互方式的有效场景仅限于在responder chain上的UIResponder对象上。算法
网上关于MVVM的文章不少并且每一个人的理解可能都有小小的差异,这里不作赘述,这里说说我在项目中所用到的MVVM,若是错误,请看官多多指教。个人tableView定义在Controller中,其代理方法也在Contriller实现,ViewModel为其提供必要的数据:设计模式
头文件中:bash
#import <UIKit/UIKit.h>
@interface QFViewModel : NSObject
- (NSInteger)numberOfRowsInSection:(NSInteger)section;
- (id<QFModelProtocol>)tableView:(UITableView *)tableView itemForRowAtIndexPath:(NSIndexPath *)indexPath;
@end
复制代码
实现文件中两个关键方法:ide
- (NSInteger)numberOfRowsInSection:(NSInteger)section {
return self.dataArray.count;
}
- (id<QFModelProtocol>)tableView:(UITableView *)tableView itemForRowAtIndexPath:(NSIndexPath *)indexPath {
id<QFModelProtocol> model = self.dataArray[indexPath.row];
return model;
}
复制代码
这里用到了协议Protocol作解耦,两个协议,一个视图层的协议QFViewProtocol
,一个模型的协议QFModelProtocol
ui
/**
协议用于保存每一个cell的数据源设置方法,也能够不用,直接在每一个类型的cell头文件中定义,考虑到开放封闭原则,建议使用
*/
@protocol QFViewProtocol <NSObject>
/**
经过model 配置cell展现
@param model model
*/
- (void)configCellDateByModel:(id<QFModelProtocol>)model;
复制代码
#import <UIKit/UIKit.h>
/**
协议用于保存每一个model对应cell的重用标志符和行高,也能够不使用这个协议 直接在对一个的model里指明
*/
@protocol QFModelProtocol <NSObject>
- (NSString *)identifier;
- (CGFloat)height;
@end
复制代码
在控制器层中直接建立而且addSubView:spa
- (void)initAppreaence {
[self.tableView registerClass:[QFCellOne class] forCellReuseIdentifier:kCellOneIdentifier];
[self.tableView registerClass:[QFCellTwo class] forCellReuseIdentifier:kCellTwoIdentifier];
[self.view addSubview:self.tableView];
}
复制代码
在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];
复制代码
在《大话设计模式》一书中,使用了商场打折的案例分析了策略模式对于不一样算法的封装,有兴趣能够去看看,这里咱们使用策略模式封装的是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];
的index从2开始而不是0,由于还有两个隐藏参数self和_cmd占用了两个[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];
}
复制代码
本篇到此结束了,总结起来,用到的东西仍是很多,不少东西都值得深刻:
Demo演示
有任何意见和建议欢迎交流指导,若是能够,请顺手给个star。
感谢评论区各位的赐教,纠正了我把tableView定义在VM中的颠覆性错误!
最后,万分感谢Casa大佬的分享!
一种基于ResponderChain的对象交互方式