基於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
相關文章
相關標籤/搜索