在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的對象交互方式