前言
总的来说试图控制器是代码最多的文件并且大多都是不可复用的代码。下面将教你做到文件瘦身、代码复用、合理代码分配。
分离数据源与协议类
一句话就是将UITableViewDataSource
部分的代码单独写成一个类,通过继承它达到重用。
栗子,这里有一个PhotosViewController
类遵循如下的方法:
# pragma mark Pragma
- (Photo*)photoAtIndexPath:(NSIndexPath*)indexPath {
return photos[(NSUInteger)indexPath.row];
}
- (NSInteger)tableView:(UITableView*)tableView
numberOfRowsInSection:(NSInteger)section {
return photos.count;
}
- (UITableViewCell*)tableView:(UITableView*)tableView
cellForRowAtIndexPath:(NSIndexPath*)indexPath {
PhotoCell* cell = [tableView dequeueReusableCellWithIdentifier:PhotoCellIdentifier
forIndexPath:indexPath];
Photo* photo = [self photoAtIndexPath:indexPath];
cell.label.text = photo.name;
return cell;
}
上述代码有数组大量的处理。我们将数组操作相关得代码搬到我们自己创建的类。我们根据业务需求以及“癖好”使用block来配置cell:
@implementation ArrayDataSource
- (id)itemAtIndexPath:(NSIndexPath*)indexPath {
return items[(NSUInteger)indexPath.row];
}
- (NSInteger)tableView:(UITableView*)tableView
numberOfRowsInSection:(NSInteger)section {
return items.count;
}
- (UITableViewCell*)tableView:(UITableView*)tableView
cellForRowAtIndexPath:(NSIndexPath*)indexPath {
id cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier
forIndexPath:indexPath];
id item = [self itemAtIndexPath:indexPath];
configureCellBlock(cell,item);
return cell;
}
@end
可以用专门定制类生成的实例去执行上述三个方法所要执行的任务。
void (^configureCell)(PhotoCell*, Photo*) = ^(PhotoCell* cell, Photo* photo) {
cell.label.text = photo.name;
};
photosArrayDataSource = [[ArrayDataSource alloc] initWithItems:photos
cellIdentifier:PhotoCellIdentifier
configureCellBlock:configureCell];
self.tableView.dataSource = photosArrayDataSource;
同样可以在这个剥离出的类中实现tableView:commitEditingStyle:forRowAtIndexPath:
诸如此类的方法。
这样做的好处分离出来的代码我们可以进行独立测试。
在使用CoreData的时候不是使用Array作为数据源支撑,而是依赖一个数据抓取的控制器。它实现了所有刷新,分段,删除的功能。你可以创建一个实例实现抓取数据请求逻辑以及配置cell逻辑。
更进一步的说,这种分离代理成抽象类的方法可以做的更加通用,比如同时使用表格视图与集合视图这样的好处是增加了代码的可重用度。
将逻辑代码移到数据层
如下的代码是在控制器中获取激活优先级的用户:
- (void)loadPriorities {
NSDate* now = [NSDate date];
NSString* formatString = @"startDate <= %@ AND endDate >= %@";
NSPredicate* predicate = [NSPredicate predicateWithFormat:formatString, now, now];
NSSet* priorities = [self.user.priorities filteredSetUsingPredicate:predicate];
self.priorities = [priorities allObjects];
}
为了让逻辑更加清晰简洁,我们将这些代码移到用户类,控制器中代码将得到简化:
- (void)loadPriorities {
self.priorities = [self.user currentPriorities];
}
被搬走的代码在User+Extensions.m:
- (NSArray*)currentPriorities {
NSDate* now = [NSDate date];
NSString* formatString = @"startDate <= %@ AND endDate >= %@";
NSPredicate* predicate = [NSPredicate predicateWithFormat:formatString, now, now];
return [[self.priorities filteredSetUsingPredicate:predicate] allObjects];
}
当逻辑代码不是移入一个数据模型可以解决的时候,我们可以把它丢到数据仓库里面:
创建数据仓库类
当我们要从一个文件获取数据并加载它们的时候,我们的视图控制器:
- (void)readArchive {
NSBundle* bundle = [NSBundle bundleForClass:[self class]];
NSURL *archiveURL = [bundle URLForResource:@"photodata"
withExtension:@"bin"];
NSAssert(archiveURL != nil, @"Unable to find archive in bundle.");
NSData *data = [NSData dataWithContentsOfURL:archiveURL
options:0
error:NULL];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
_users = [unarchiver decodeObjectOfClass:[NSArray class] forKey:@"users"];
_photos = [unarchiver decodeObjectOfClass:[NSArray class] forKey:@"photos"];
[unarchiver finishDecoding];
}
我们知道视图控制器肯定不是干这个的。我们创建一个仓库来做这些活儿。将这些代码分离出去,便于重用,测试主要能保证我们的控制器干净。这个数据仓库作用是数据加载,获取还有创建数据层。数据仓库还可以叫做服务层以及仓储层。
将页面服务逻辑移到数据层
处理方法相同,分离一个抽象类进行单独处理。回调处理要做完整,包括数据抓取以及错误处理。
将视图代码移动带视图层
复杂层级的用XIB或者SB(其实都该这么搞)
通信
通信是控制器的主要任务,这部分的代码我们也需要尽可能的精简。
控制器与数据对象的通信自然不用说了,KVO等模式分分钟搞定。但是控制器之间的通信往往不那么方便。
上述的问题通常出现于我们的控制器有一些状态量并且与多个视图控制器进行通信的时候。。通常,将这些状态放到一个独立的对象,并将这个对象传给视图控制器通过这样可以来观察这个状态。这样做的好处是代码可以集中管理(不用在内嵌代理)。这是个复杂的命题,后续做完整的处理。
总结
编写高度解耦、简洁、可维护的视图控制器!