一、引言 项目中很早就引入了Nimbus框架,一直在搞天气动画,没时间看这个东西。最近要做新的feature,跟table相关。于是花了一下午读了读相关部分的源码。Git上是这么介绍Nimbus的——The iOS framework that grows only as fast as its documentation.一看文档和注释,果然不错,读起来一气呵成,很舒服。
二、整体思路 众所周知,生成一个UITableView,一般要设置两个东西:dataSource和delegate。其中,dataSource来控制tableview上展示的内容,而delegate用来通知tableview上发生的各种事件,如cell被点击等等。通常我们把这两个委托设置为UITableViewController,让UITableViewController同时实现UITableViewDataSource和UITableViewDelegate protocol。而 Nimbus根据这两个特性造了两个类分别是NITableViewModel(实现UITableViewDataSource,用来设置数据源dataSource)和NITableViewActions(实现UITableViewDelegate,触发tableview上的各种事件)。
1 2 3 4 5 self .testTableView.delegate = _actions;self .testTableView.dataSource = _model;
三、从数据源到具体Cell 我们先来看看怎么从数据源生成具体的Cell。Nimbus把该过程分为2部分:数据管理和Cell生成。其中数据管理由NITableViewModel实现,而Cell生成是实现NITableViewModelDelegate protocol,可能有多种方式。
1数据管理 首先来看看数据管理,NITableViewModel定义。
1 2 3 4 5 @interface NITableViewModel : NSObject <UITableViewDataSource > - (id )initWithDelegate:(id <NITableViewModelDelegate>)delegate; - (id )initWithListArray:(NSArray *)sectionedArray delegate:(id <NITableViewModelDelegate>)delegate; - (id )initWithSectionedArray:(NSArray *)sectionedArray delegate:(id <NITableViewModelDelegate>)delegate; @end
其中NITableViewModelDelegate,是指定Cell的生成委托,对应第二步,我们先忽略。 其中sectionedArray是指定的数据内容。
其中调用方式可能如上所示,如果sectionedArray里面的一项是NSString则认为这是一个section头部(标题),如果是一个NITableViewModelFooter对象则认为是一个section的尾部。其余每一项则认为是一个Cell上的内容。如上例,该sectionedArray展示了3个section,第一个section的标题是@”Section 1”,内部有两个cell,具体内容分别由[NSDictionary dictionaryWithObject:@”Row 1” forKey:@”title”]和[NSDictionary dictionaryWithObject:@”Row 2” forKey:@”title”]表示。如此跟table视觉上的排布一致。得到这个sectionedArray后,NITableViewModel对该数组进行一顿解析,在内部用一些更加友好的数据管理起来。我们可以先把NITableViewModel简单的理解为:解析sectionedArray数组,了解里面有哪些section哪些cell,在UITableViewDataSource相应的函数里面返回解析过后的数值。
2Cell生成 通过上文我们知道,NITableViewModel实现了UITableViewDataSource protocol,该protocol有个一函数用来生成cell的
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
NITableViewModel职责有限,只能管理数据源不管你生成Cell,所以他把这个东西抛给他的委托,NITableViewModelDelegate,让这个委托来生成Cell。我们前文提到生成Cell有多种方式,这些方式都是通过实现NITableViewModelDelegate得来的。 UITableViewController实现NITableViewModelDelegate
1 2 3 4 5 6 7 8 9 10 11 - (UITableViewCell *)tableViewModel: (NITableViewModel *)tableViewModel cellForTableView: (UITableView *)tableView atIndexPath: (NSIndexPath *)indexPath withObject: (id )object { id object = [tableViewModel objectAtIndexPath:indexPath]; }
利用NICellFactory实现NITableViewModelDelegate
若利用此种方式实现Cell生成,则必须要求第一步sectionedArray表示cell内容的对象是一个NICellObject对象。其中NICellObject类定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @protocol NICellObject <NSObject >@required - (Class)cellClass; @optional - (UITableViewCellStyle )cellStyle; @end @interface NICellObject : NSObject <NICellObject >- (id )initWithCellClass:(Class)cellClass userInfo:(id )userInfo; - (id )initWithCellClass:(Class)cellClass; + (id )objectWithCellClass:(Class)cellClass userInfo:(id )userInfo; + (id )objectWithCellClass:(Class)cellClass; @property (nonatomic , strong ) id userInfo;@end
通过类定义,我们不难发现,NICellObject实际就是一个带cellClass信息的类,也就是说NICellFactory要求我们传到model的sectionedArray里面的cell内容对象都是带cell class信息的。通过改造,我们可以把indexPath为section0 row0的object变为[NICellObject objectWithCellClass:[UITableViewCell class] userInfo:[NSDictionary dictionaryWithObject:@”Row 1” forKey:@”title”] ]; 具体生成函数如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 + (UITableViewCell *)tableViewModel:(NITableViewModel *)tableViewModel cellForTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath withObject:(id )object { UITableViewCell * cell = nil ; if ([object respondsToSelector:@selector (cellClass)]) { Class cellClass = [object cellClass]; cell = [self cellWithClass:cellClass tableView:tableView object:object]; } else if ([object respondsToSelector:@selector (cellNib)]) { UINib * nib = [object cellNib]; cell = [self cellWithNib:nib tableView:tableView indexPath:indexPath object:object]; } NIDASSERT(nil != cell); return cell; } + (UITableViewCell *)cellWithClass:(Class)cellClass tableView:(UITableView *)tableView object:(id )object { UITableViewCell * cell = nil ; NSString * identifier = NSStringFromClass (cellClass); if ([cellClass respondsToSelector:@selector (shouldAppendObjectClassToReuseIdentifier)] && [cellClass shouldAppendObjectClassToReuseIdentifier]) { identifier = [identifier stringByAppendingFormat:@".%@" , NSStringFromClass ([object class ])]; } cell = [tableView dequeueReusableCellWithIdentifier:identifier]; if (nil == cell) { UITableViewCellStyle style = UITableViewCellStyleDefault ; if ([object respondsToSelector:@selector (cellStyle)]) { style = [object cellStyle]; } cell = [[cellClass alloc] initWithStyle:style reuseIdentifier:identifier]; } if ([cell respondsToSelector:@selector (shouldUpdateCellWithObject:)]) { [(id <NICell>)cell shouldUpdateCellWithObject:object]; } return cell; }
Cell一气呵成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @interface TestCell : UITableViewCell <NICell >@end @interface TestCell ()@property (nonatomic , weak ) UILabel * lbl;@end @implementation TestCell - (BOOL )shouldUpdateCellWithObject:(id )object { if (!_lbl) { UILabel * tmp = [[UILabel alloc] initWithFrame:self .bounds]; self .lbl = tmp; [self .contentView addSubview:_lbl]; } NSDictionary * data = ((NICellObject*)object).userInfo; _lbl.text = data[@"title" ]; return YES ; } @end UITableView * tmpView = [[UITableView alloc] initWithFrame:self .view.bounds style:UITableViewStyleGrouped ];self .testView = tmpView;[self .view addSubview:_testView]; NSArray * contentAry = @[ [NICellObject objectWithCellClass:[TestCell class ] userInfo:@{ @"title" : @"hello" }], [NICellObject objectWithCellClass:[TestCell class ] userInfo:@{ @"title" : @"nimbus" }], [NICellObject objectWithCellClass:[TestCell class ] userInfo:@{ @"title" : @"what" }], [NICellObject objectWithCellClass:[TestCell class ] userInfo:@{ @"title" : @"are" }] ]; self .model = [[NITableViewModel alloc] initWithSectionedArray:contentAry delegate:(id )[NICellFactory class ]]; self .testView.dataSource = _model;
四、tableview事件处理 说完了数据源展示,我们在来探索一下table事件处理。Nimbus用到了NITableViewActions类,该类接口定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @interface NITableViewActions : NIActions <UITableViewDelegate >#pragma mark Forwarding - (id <UITableViewDelegate >)forwardingTo:(id <UITableViewDelegate >)forwardDelegate; - (void )removeForwarding:(id <UITableViewDelegate >)forwardDelegate; #pragma mark Object state - (UITableViewCellAccessoryType )accessoryTypeForObject:(id )object; - (UITableViewCellSelectionStyle )selectionStyleForObject:(id )object; #pragma mark Configurable Properties @property (nonatomic , assign ) UITableViewCellSelectionStyle tableViewCellSelectionStyle;@end
可以看出该类实现了UITableViewDelegate protocol,因此可以当作tableview的事件处理类。NITableViewActions继承自NIActions类,该类又定义如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @interface NIActions : NSObject - (id )initWithTarget:(id )target; #pragma mark Mapping Objects - (id )attachToObject:(id <NSObject >)object tapBlock:(NIActionBlock)action; - (id )attachToObject:(id <NSObject >)object detailBlock:(NIActionBlock)action; - (id )attachToObject:(id <NSObject >)object navigationBlock:(NIActionBlock)action; - (id )attachToObject:(id <NSObject >)object tapSelector:(SEL)selector; - (id )attachToObject:(id <NSObject >)object detailSelector:(SEL)selector; - (id )attachToObject:(id <NSObject >)object navigationSelector:(SEL)selector; #pragma mark Mapping Classes - (void )attachToClass:(Class)aClass tapBlock:(NIActionBlock)action; - (void )attachToClass:(Class)aClass detailBlock:(NIActionBlock)action; - (void )attachToClass:(Class)aClass navigationBlock:(NIActionBlock)action; - (void )attachToClass:(Class)aClass tapSelector:(SEL)selector; - (void )attachToClass:(Class)aClass detailSelector:(SEL)selector; - (void )attachToClass:(Class)aClass navigationSelector:(SEL)selector; #pragma mark Object State - (BOOL )isObjectActionable:(id <NSObject >)object; + (id )objectFromKeyClass:(Class)keyClass map:(NSMutableDictionary *)map; @end
从类接口可以直观的看出,该类是把block块或者selector绑定到一个对象上。应该是在内部实际维护了从对象到block块或者selector的关系。
1 2 3 4 5 6 7 @interface NIActions ()@property (nonatomic , strong ) NSMutableDictionary * objectToAction;@property (nonatomic , strong ) NSMutableDictionary * classToAction;@property (nonatomic , strong ) NSMutableSet * objectSet;@end
再从这个类的extention,果断看出了端倪,证明我们的猜测不错。 我们来看一个attach方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 - (id )attachToObject:(id <NSObject >)object tapBlock:(NIActionBlock)action { [self .objectSet addObject:object]; [self actionForObject:object].tapAction = action; return object; } - (NIObjectActions *)actionForObject:(id <NSObject >)object { id key = [self keyForObject:object]; NIObjectActions* action = [self .objectToAction objectForKey:key]; if (nil == action) { action = [[NIObjectActions alloc] init]; [self .objectToAction setObject:action forKey:key]; } return action; } @interface NIObjectActions : NSObject @property (nonatomic , copy ) NIActionBlock tapAction;@property (nonatomic , copy ) NIActionBlock detailAction;@property (nonatomic , copy ) NIActionBlock navigateAction;@property (nonatomic ) SEL tapSelector;@property (nonatomic ) SEL detailSelector;@property (nonatomic ) SEL navigateSelector;@end
通过以上函数可以看出NIActions内部用到了NIObjectActions这个数据结构来表示各种action,而NIActions维护了一个从object的hash key到NIObjectActions对象的关系。 我再回过头来看NITableViewActions类的UITableViewDelegate实现,具体函数如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 - (void )tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { NIDASSERT([tableView.dataSource isKindOfClass:[NITableViewModel class ]]); if ([tableView.dataSource isKindOfClass:[NITableViewModel class ]]) { NITableViewModel* model = (NITableViewModel *)tableView.dataSource; id object = [model objectAtIndexPath:indexPath]; if ([self isObjectActionable:object]) { NIObjectActions* action = [self actionForObjectOrClassOfObject:object]; BOOL shouldDeselect = NO ; if (action.tapAction) { shouldDeselect = action.tapAction(object, self .target, indexPath); } if (action.tapSelector && [self .target respondsToSelector:action.tapSelector]) { NSMethodSignature *methodSignature = [self .target methodSignatureForSelector:action.tapSelector]; NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature]; invocation.selector = action.tapSelector; if (methodSignature.numberOfArguments >= 3 ) { [invocation setArgument:&object atIndex:2 ]; } if (methodSignature.numberOfArguments >= 4 ) { [invocation setArgument:&indexPath atIndex:3 ]; } [invocation invokeWithTarget:self .target]; NSUInteger length = invocation.methodSignature.methodReturnLength; if (length > 0 ) { char *buffer = (void *)malloc(length); memset(buffer, 0 , sizeof (char ) * length); [invocation getReturnValue:buffer]; for (NSUInteger index = 0 ; index < length; ++index) { if (buffer[index]) { shouldDeselect = YES ; break ; } } free(buffer); } } if (shouldDeselect) { [tableView deselectRowAtIndexPath:indexPath animated:YES ]; } if (action.navigateAction) { action.navigateAction(object, self .target, indexPath); } if (action.navigateSelector && [self .target respondsToSelector:action.navigateSelector]) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" [self .target performSelector:action.navigateSelector withObject:object withObject:indexPath]; #pragma clang diagnostic pop } } } for (id <UITableViewDelegate > delegate in self .forwardDelegates) { if ([delegate respondsToSelector:_cmd]) { [delegate tableView:tableView didSelectRowAtIndexPath:indexPath]; } } }
所以一个table的action可能是这样的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 self .actions = [[NITableViewActions alloc] initWithTarget:self ];self .testView.delegate = _actions;NSArray * contentAry = @[ [_actions attachToObject:[NICellObject objectWithCellClass:[TestCell class ] userInfo:@{ @"title" : @"hello" }] tapBlock:^BOOL (id object, id target, NSIndexPath * indexPath) { NSLog (@"%@" , indexPath); return YES ; }], [NICellObject objectWithCellClass:[TestCell class ] userInfo:@{ @"title" : @"nimbus" }], [NICellObject objectWithCellClass:[TestCell class ] userInfo:@{ @"title" : @"what" }], [NICellObject objectWithCellClass:[TestCell class ] userInfo:@{ @"title" : @"are" }] ]; self .model = [[NITableViewModel alloc] initWithSectionedArray:contentAry delegate:(id )[NICellFactory class ]]; self .testView.dataSource = _model;
model里面表示cell内容的对象可以被attach到actions对象上。