基于RAC的通用TableView

  最近公司的一个新项目的1.0版本开发完了,但是对于这么一个初期的项目,部分VC的代码行数仍然超过300行。我也开始感觉到有种(Massive)MVC的趋势,而且部分界面控件的创建方法还是略略有点Magic。于是我开始寻求新的架构,来改善当前的状况。我想起了之前听说过的ReactiveCocoa,加上最近Ray神和Objcio.cn的介绍,我也开始了RAC的“修炼”。
  不过,RAC的学习不但需要了解其API的作用,更重要的是用RAC的思维去思考。如果用传统的MVC架构思维,我可以很快速写出一个简单的框架,但是学习RAC和MVVM,就发现原来很多理所当然的东西也要仔细去思考。对于初学者而言,RAC并不是很友好,但掌握基本用法后,就已经可以感受到RAC的优势。
  在学习的时候,我看到了Ray神上面用Signal和Command来代替UITableViewDataSource和UITableViewDelegate的helper。不过,本人还是觉得不太满意,毕竟需要额外的helper,而且限定cell必须来自xib。于是基于Ray神的基础上,我把这套模式搬到了UITableView层上,同时也支持多个section和cell的定制。这类相对常用的控件,我不想把API设计得过于复杂,所以对于有相对特殊的要求还是需要实现DataSource和Delegate。本人意在把Ray神的思路整合到控件层,并不打算写高大上的控件。

 

 

 1 //
 2 //  PINKBindCellProtocol.h
 3 //
 4 
 5 #import <Foundation/Foundation.h>
 6 
 7 @protocol PINKBindCellProtocol <NSObject>
 8 
 9 - (void)bindCellViewModel:(id)viewModel;
10 
11 @end

 

 

 

 

 1 //
 2 //  PINKBindTableView.h
 3 //
 4 
 5 #import <UIKit/UIKit.h>
 6 
 7 @protocol PINKBindCellProtocol;
 8 
 9 typedef UITableViewCell *(^PINKBindTableViewCreateCellBlock)(NSIndexPath *indexPath);
10 
11 @interface PINKBindTableView : UITableView
12 
13 @property (nonatomic, getter = isAutoCheckDataSource) BOOL autoCheckDataSource;
14 @property (nonatomic, getter = isAutoDeselect) BOOL autoDeselect;
15 
16 @property (nonatomic, readonly) id<UITableViewDataSource> realDataSource;
17 @property (nonatomic, readonly) id<UITableViewDelegate> realDelegate;
18 
19 - (void)setDataSourceSignal:(RACSignal *)sourceSignal
20            selectionCommand:(RACCommand *)selection
21                   cellClass:(Class<PINKBindCellProtocol>)cellClass;
22 
23 - (void)setDataSourceSignal:(RACSignal *)sourceSignal
24            selectionCommand:(RACCommand *)selection
25             createCellBlock:(PINKBindTableViewCreateCellBlock)createCellBlock;
26 
27 @end
28 
29 //以下是截获了的方法,继承子类时可以直接重写,但还是要调用super
30 @interface PINKBindTableView (UITableViewDataSourceIntercept)
31 
32 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;
33 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
34 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
35 
36 @end
37 
38 @interface PINKBindTableView (UITableViewDelegateIntercept)
39 
40 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
41 
42 @end

 

 

  1 //
  2 //  PINKBindTableView.m
  3 //
  4 
  5 #import "PINKBindTableView.h"
  6 
  7 #import "MessageInterceptor.h"
  8 
  9 #import "PINKBindCellProtocol.h"
 10 
 11 typedef NS_OPTIONS(NSInteger, PINKBindTableView_DataSource_MethodType) {
 12     PINKBindTableView_DataSource_MethodType_numberOfSectionsInTableView  = 1 << 0,
 13     PINKBindTableView_DataSource_MethodType_numberOfRowsInSection        = 1 << 1,
 14     PINKBindTableView_DataSource_MethodType_cellForRowAtIndexPath        = 1 << 2,
 15 };
 16 
 17 @interface PINKBindTableView ()<UITableViewDataSource, UITableViewDelegate>
 18 {
 19     MessageInterceptor *_dataSourceInterceptor;
 20     MessageInterceptor *_delegateInterceptor;
 21     
 22     PINKBindTableView_DataSource_MethodType _dataSourceMethodType;
 23 }
 24 
 25 /**
 26  *  tableData为nil时,若dataSource或delegate实现了对应方法,则调用。
 27  */
 28 @property (nonatomic, strong) NSArray *tableData;
 29 @property (nonatomic, strong) RACCommand *didSelectedCommand;
 30 @property (nonatomic, unsafe_unretained) Class<PINKBindCellProtocol> cellClass;
 31 @property (nonatomic, strong) NSString *cellReuseIdentifier;
 32 @property (nonatomic, copy) PINKBindTableViewCreateCellBlock createCellBlock;
 33 
 34 @end
 35 
 36 @implementation PINKBindTableView
 37 
 38 - (id)initWithFrame:(CGRect)frame
 39 {
 40     self = [super initWithFrame:frame];
 41     
 42     if (self) {
 43         [self p_InitPINKBindTableView];
 44     }
 45     
 46     return self;
 47 }
 48 
 49 - (instancetype)initWithCoder:(NSCoder *)aDecoder
 50 {
 51     self = [super initWithCoder:aDecoder];
 52     
 53     if (self) {
 54         [self p_InitPINKBindTableView];
 55     }
 56     
 57     return self;
 58 }
 59 
 60 #pragma mark - Private Initialize
 61 - (void)p_InitPINKBindTableView
 62 {
 63     _autoCheckDataSource = YES;
 64     _autoDeselect = YES;
 65     
 66     _dataSourceInterceptor = [[MessageInterceptor alloc] init];
 67     _dataSourceInterceptor.middleMan = self;
 68     _dataSourceInterceptor.receiver = [super dataSource];
 69     [super setDataSource:(id<UITableViewDataSource>)_dataSourceInterceptor];
 70     
 71     _delegateInterceptor = [[MessageInterceptor alloc] init];
 72     _delegateInterceptor.middleMan = self;
 73     _delegateInterceptor.receiver = [super delegate];
 74     [super setDelegate:(id<UITableViewDelegate>)_delegateInterceptor];
 75 }
 76 
 77 #pragma mark - Overwrite DataSource
 78 - (void)setDataSource:(id<UITableViewDataSource>)dataSource
 79 {
 80     if (_dataSourceInterceptor) {
 81         _dataSourceInterceptor.receiver = dataSource;
 82         //UITableViewDataSource有类似缓存机制优化,所以先设置nil
 83         [super setDataSource:nil];
 84         [super setDataSource:(id<UITableViewDataSource>)_dataSourceInterceptor];
 85         
 86         [self updateDataSourceMethodType];
 87     } else {
 88         [super setDataSource:dataSource];
 89     }
 90 }
 91 
 92 - (id<UITableViewDataSource>)realDataSource
 93 {
 94     if (_dataSourceInterceptor) {
 95         return _dataSourceInterceptor.receiver;
 96     } else {
 97         return [super dataSource];
 98     }
 99 }
100 
101 #pragma mark - Overwrite Delegate
102 - (void)setDelegate:(id<UITableViewDelegate>)delegate
103 {
104     if (_delegateInterceptor) {
105         _delegateInterceptor.receiver = delegate;
106         
107         [super setDelegate:nil];
108         [super setDelegate:(id<UITableViewDelegate>)_delegateInterceptor];
109     } else {
110         [super setDelegate:delegate];
111     }
112 }
113 
114 - (id<UITableViewDelegate>)realDelegate
115 {
116     if (_delegateInterceptor) {
117         return _delegateInterceptor.receiver;
118     } else {
119         return [super delegate];
120     }
121 }
122 
123 #pragma mark - UITableViewDataSource
124 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
125 {
126     if (!_tableData &&
127         _dataSourceMethodType & PINKBindTableView_DataSource_MethodType_numberOfSectionsInTableView) {
128         return [_dataSourceInterceptor.receiver numberOfSectionsInTableView:tableView];
129     } else
130         return _tableData.count;
131 }
132 
133 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
134 {
135     if (!_tableData &&
136         _dataSourceMethodType & PINKBindTableView_DataSource_MethodType_numberOfRowsInSection) {
137         return [_dataSourceInterceptor.receiver tableView:tableView numberOfRowsInSection:section];
138     } else
139         return [_tableData[section] count];
140 }
141 
142 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
143 {
144     if (!_tableData &&
145         _dataSourceMethodType & PINKBindTableView_DataSource_MethodType_cellForRowAtIndexPath) {
146         return [_dataSourceInterceptor.receiver tableView:tableView cellForRowAtIndexPath:indexPath];
147     } else {
148         UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:_cellReuseIdentifier];
149         if (!cell) {
150             if (_cellClass) {
151                 cell = [[(Class)_cellClass alloc] init];
152             } else if (_createCellBlock) {
153                 cell = _createCellBlock(indexPath);
154             }
155         }
156         [(id<PINKBindCellProtocol>)cell bindCellViewModel:_tableData[indexPath.section][indexPath.row]];
157         
158         return cell;
159     }
160 }
161 
162 #pragma mark - UITableViewDelegate
163 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
164 {
165     if (_autoDeselect) {
166         [tableView deselectRowAtIndexPath:indexPath animated:YES];
167     }
168     
169     if (_tableData && _didSelectedCommand) {
170         [_didSelectedCommand execute:_tableData[indexPath.section][indexPath.row]];
171     }
172     
173     if ([_delegateInterceptor.receiver respondsToSelector:@selector(tableView:didSelectRowAtIndexPath:)]) {
174         [_delegateInterceptor.receiver tableView:tableView didSelectRowAtIndexPath:indexPath];
175     }
176 }
177 
178 #pragma mark - 缓存DataSource方法
179 - (void)updateDataSourceMethodType
180 {
181     _dataSourceMethodType = 0;
182     
183     if ([_dataSourceInterceptor.receiver respondsToSelector:@selector(numberOfSectionsInTableView:)]) {
184         _dataSourceMethodType |= PINKBindTableView_DataSource_MethodType_numberOfSectionsInTableView;
185     }
186     
187     if ([_dataSourceInterceptor.receiver respondsToSelector:@selector(tableView:numberOfRowsInSection:)]) {
188         _dataSourceMethodType |= PINKBindTableView_DataSource_MethodType_numberOfRowsInSection;
189     }
190     
191     if ([_dataSourceInterceptor.receiver respondsToSelector:@selector(tableView:cellForRowAtIndexPath:)]) {
192         _dataSourceMethodType |= PINKBindTableView_DataSource_MethodType_cellForRowAtIndexPath;
193     }
194 }
195 
196 #pragma mark - API
197 - (void)setDataSourceSignal:(RACSignal *)sourceSignal
198            selectionCommand:(RACCommand *)selection
199                   cellClass:(Class<PINKBindCellProtocol>)cellClass
200 {
201     self.cellClass = cellClass;
202     self.createCellBlock = nil;
203     self.didSelectedCommand = selection;
204     [self configCellReuseIdentifierAndCellHeight];
205     
206     @weakify(self);
207     [sourceSignal subscribeNext:^(NSArray *source) {
208         @strongify(self);
209         self.tableData = source;
210         [self reloadData];
211     }];
212 }
213 
214 - (void)setDataSourceSignal:(RACSignal *)sourceSignal
215            selectionCommand:(RACCommand *)selection
216             createCellBlock:(PINKBindTableViewCreateCellBlock)createCellBlock
217 {
218     self.cellClass = nil;
219     self.createCellBlock = createCellBlock;
220     self.didSelectedCommand = selection;
221     [self configCellReuseIdentifierAndCellHeight];
222     
223     @weakify(self);
224     [sourceSignal subscribeNext:^(NSArray *source) {
225         @strongify(self);
226         self.tableData = source;
227         [self reloadData];
228     }];
229 }
230 
231 - (void)configCellReuseIdentifierAndCellHeight
232 {
233     UITableViewCell *oneCell = nil;
234     if (_cellClass) {
235         oneCell = [[(Class)_cellClass alloc] init];
236     } else if (_createCellBlock) {
237         oneCell = _createCellBlock([NSIndexPath indexPathForRow:0 inSection:0]);
238     }
239     _cellReuseIdentifier = oneCell.reuseIdentifier;
240     self.rowHeight = oneCell.frame.size.height;
241 }
242 
243 #pragma mark - Properties
244 - (void)setTableData:(NSArray *)tableData
245 {
246     if (_autoCheckDataSource &&
247         tableData.count &&
248         ![tableData[0] isKindOfClass:[NSArray class]]) {
249         _tableData = @[tableData];
250     } else {
251         _tableData = tableData;
252     }
253 }
254 
255 @end
posted @ 2014-07-23 13:23  Pinka  阅读(1688)  评论(0编辑  收藏  举报