轻量化 ViewController,读文章做的总结

轻量化 ViewControllers 顾名思义就是把 ViewController 的代码进行简化,让控制器更简单、更清晰。

把 DataSource 和其他 protocol 分离出控制器

因为做项目很多控制器都是 TableViewController,所以必须要有数据源(TableViewDataSource)输入,一般 DataSource 的 3 个方法都在控制器里。
我们的目的就是要把它分离出来,方法就是自定义一个类,然后控制器调用这个类。这个数据源类是通用的,别的 TableViewController 或者有定义 tableView/collectionView 的控制器都可以调用自定义 ArrayDataSource 类。

  1. 定义一个 Block,用于传递 cell 和数据
1
typedef void (^TableViewCellPassBlock)(id cell, id item);
  1. 定义一个方法给控制器调用,用于初始化 ArrayDataSource 类(需要暴露在 .h 文件中)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@property (nonatomic, strong) NSArray *items;
@property (nonatomic, copy) NSString *cellIdentifier;
@property(nonatomic, copy) TableViewCellPassBlock passBlock;
/**

* 初始化数据源的方法
* @param anItems 用于接受数据的数组
* @param aCellIdentifier 接收cell的ID
* @param aPassBlock 接收传递的Block
*/
- (id)initWithItems:(NSArray *)anItems cellIdentifier:(NSString *)aCellIdentifier passBlock(TableViewCellPassBlock)aPassBlock {
self = [super init];
if(self){
self.items = anItems;
self.cellIdentifier = aCellIdentifier;
self.passBlock = [aPassBlock copy];
}
return self;
}
  1. 然后就是数据源的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/** 这个方法找出数组的数据 后面要用到*/
- (id)itemAtIndexPath:(NSIndexPath *)indexPath
{
return self.items[(NSUInteger) indexPath.row];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.items.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: self.cellIdentifier forIndexPath: indexPath];
id item = [self itemAtIndexPath: indexPath];
self.passBlock(cell, item);
return cell;
}

ArrayDataSource 类就这样定义完成了。
接下来是控制器的调用。
只需要写以下几行代码便可以设置 tableView 的数据源,不用每个控制器都写那三个方法。

1
2
3
4
5
6
7
8
9
10
//数组传数据是接下来要简化的,后面会详细说明
NSArray *arr = [AppDelegate sharedDelegate].returnModel.returnData;
//使用Block回调,传递cell和模型数据
TableViewCellPassBlock passBlock = ^(TestModelCell *cell,TestModel *model){
//测试方法,只是传递model给cell,这个方法用分类写的,自定义cell的分类
[cell setName: model];
};
//初始化并设置tableView的数据源
self.dataSource = [[ArrayDataSource alloc] initWithItems: arr cellIdentifier:@"TestModelCell" passBlock: passBlock];
self.tableView.dataSource = self.dataSource;

二、将业务逻辑放到 model 中

一些跟控制器无关的代码可以放到模型中。
测试代码我写了一个 TestModel,只有一个属性和一个初始化方法。

1
2
3
@property (nonatomic, copy) NSString *name;

- (instancetype)initWithName:(NSString *)name;

然后又写了一个 model,因为要返回数据所以我叫 ReturnModel。
在 .h 文件中暴露的方法:

1
2
3
4
5
//类方法,创建的时候用这个方法
+ (instancetype)returnModel;

//这个方法用于返回数据
- (NSArray *)returnData;

然后在 .m 文件中:

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
+ (instancetype)returnModel
{
return [[self alloc] init];
}

- (id)init
{
self = [super init];
if (self) {
//调用方法执行业务逻辑的方法
[self doSomething];
}

return self;
}

//可以在这个方法中执行与控制器无关的业务逻辑
- (void)doSomething
{
NSLog(@"try to do something");
}

//在这个方法中返回模型数据,数据略

- (NSArray *)returnData
{
  NSArray *array = [[NSArray alloc] initWithObject:...];
  return array;
}

思路就是在返回数据的模型类中执行业务逻辑,在创建类的时候执行。
在 AppDelegate 中添加方法和属性,并且暴露在 .h 文件中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
+ (instancetype)sharedDelegate
{
return [UIApplication sharedApplication].delegate;
}

@synthesize returnModel = _returnModel;

- (ReturnModel *)returnModel
{
if (_returnModel == nil) {
_returnModel = [ReturnModel returnModel];
}
return _returnModel;
}

所以控制器直接调用方法得到数据。

1
NSArray *arr = [AppDelegate sharedDelegate].returnModel.returnData;

这时候控制器已经很简化了。
可以把网络请求也放在 model 层。

参考

Obj中国的#1

探索

文中只写了 3 种方法,后来我在用的时候要用到其他数据源方法的时候想,为什么不把其他方法也写进去,把数据源完全分离开来。
例如 Section 的 title。
接口方法

1
- (void)setSelectIndexTitle:(NSArray *)selectIndexTitles;

用一个数据接收便可:

1
2
3
4
- (void)setSectionTitle:(NSArray *)groupTitles
{
self.groupTitles = groupTitles;
}

然后在 titleForHeaderInSection 中调用即可。

1
2
3
4
5
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
NSString *title = self.groupTitles[section];
return title;
}