MVC最令人头疼的问题可能就是随着项目愈发复杂,ViewController的代码也会变得越来越冗长。阅读了objc的《Lighter View Controllers》和《Clean Table View Code》这两篇文章之后,总结了一些常用的轻量化ViewController的小技巧。
既然要简化ViewController中的代码,那么在不改变原来实现方式的前提下,唯一的方法就是把一些可以不用放在ViewController中的代码转移出去。同时,也应该理解,MVC只是一种设计典范,并没有说一定只是由Model-View-Controller这三个文件组成,而所谓的Controller也可以是一组文件。
假设原来的ViewController中有一个方法叫做“loadPriorities”,用来给自己这个类的priorities赋值,
- (void)loadPriorities {
NSDate* now = [NSDate date];
NSString* formatString = @"startDate = %@";
NSPredicate* predicate = [NSPredicate predicateWithFormat:formatString, now, now];
NSSet* priorities = [self.user.priorities filteredSetUsingPredicate:predicate];
self.priorities = [priorities allObjects];
}
我们完全可以把这段代码移动到User类的Category中,从而把loadPriorities方法简化为:
- (void)loadPriorities { self.priorities = [user currentPriorities];
}
我们都有过读写plist或者其他文件的经历,一旦读写文件或者读取到的数据处理起来较为复杂,就可以专门建立一个Store类处理这些任务。比如原来在ViewController中的这段代码就可以被分离出去:
- (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];
}
通过分离,我们就可以复用这些代码,单独测试他们,并且让 view controller 保持小巧。
Store 对象会关心数据加载、缓存和设置数据栈。它也经常被称为服务层或者仓库。
和文件读写类似,网络请求的逻辑可以转移到一个单独的Manager类或者Model的Category中去。这样做,不仅可以简化ViewController并在ViewController中利用回调block来请求网络,也可以在单独的类中实现错误处理和缓存控制等相关问题。
ViewController与Model和View之间的信息交互已经在MVC设计模式中阐述得非常清楚了,但是ViewController和其他多个ViewController之间的信息传递却是一个无法回避也并不容易的事。
一个比较好的方案是把信息放到一个单独的对象里,然后把这个对象传递给其它 view controllers,它们观察和修改这个信息。这样的好处是消息传递都在一个地方(被观察的对象)进行,而且我们也不用纠结嵌套的 delegate 回调。
UITableView在各个app中被大量的使用,同时也是ViewController简洁性的杀手。对于它的优化自然也是重中之重。
TableViewControllers 相对于标准ViewControllers 的一个特别的好处是它支持 Apple 实现的“下拉刷新”。目前,文档中唯一的使用 UIRefreshControl 的方式就是通过 table view controller 。虽然有诸多框架也实现了这一功能,但毕竟不是官方文档提供的框架,下一个版本的系统是否还支持并不好说。
TableViewControllers也有自己最大的缺点。它的 view 属性永远都是一个 table view。如果我们稍后决定在 table view 旁边显示一些东西(比如一个地图),如果不依赖于那些奇怪的技巧,估计就没什么办法了。
了解了TableViewControllers的优缺点后就应该根据实际需求选择比较合适的实现方法了。接下来就是几个优化table view的小技巧。
一旦决定把UITableViewController作为Child View Controller,最重要的一点是在UITableViewController和Parent View Controller之间建立消息传递的渠道。比如用户选择了一个 table view 中的 cell,parent view controller 需要知道这个事件来推入其他 view controller。根据使用习惯,通常最清晰的方式是为这个 table view controller 定义一个 delegate protocol,然后到 parent view controller 中去实现。parent view controller中的代码如下:
@protocol DetailsViewControllerDelegate
- (void)didSelectPhotoAttributeWithKey:(NSString *)key;
@end
- (void)didSelectPhotoAttributeWithKey:(NSString *)key
{
DetailViewController *controller = [[DetailViewController alloc] init];
controller.key = key;
[self.navigationController pushViewController:controller animated:YES];
}
DateSource中的代上基本都是围绕数组做一些事情,更针对地说,是围绕 view controller(此时它自身作为table view的DataSource) 所管理的数组做一些事情。我们可以尝试把数组相关的代码移到单独的类中,与此同时,可以使用一个 block 来设置 cell,也可以用 delegate 来做这件事。
经过简化,ViewController中的代码现在看上去是这个样子的:
void (^configureCell)(Cell*, Model*) = ^(Cell* cell, Model* model) {
cell.label.text = model.name;
};
modelsArrayDataSource = [[ArrayDataSource alloc] initWithItems:models
cellIdentifier:PhotoCellIdentifier configureCellBlock:configureCell];
self.tableView.dataSource = modelsArrayDataSource;
传入modelsArrayDataSource的block主要是在cellForRowAtIndexPath方法中用来配置cell。
这样的好处在于,我们可以单独测试这个类,再也不用写第二遍。该原则同样适用于数组之外的其他对象。除此以外,这种方法还可以扩展到其他 protocols 上面。最明显的一个就是UICollectionViewDataSource。这给我们带来了极大的灵活性;如果,在开发的某个时候想用 UICollectionView 代替 UITableView,几乎不需要对 view controller 作任何修改。甚至可以让你的 data source 同时支持这两个协议。
如果要修改Cell默认的选中或者高亮状态下的表现,可以通过代理去设置。
- (void)tableView:(UITableView *)tableView
didHighlightRowAtIndexPath:(NSIndexPath *)indexPath
{
PhotoCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.photoTitleLabel.shadowColor = [UIColor darkGrayColor];
cell.photoTitleLabel.shadowOffset = CGSizeMake(3, 3);
}
但是更好的解决方案是对Controller隐藏Cell的实现细节,把这些实现细节放到Cell内部实现,而cell仅仅对外暴露一个接口。
@implementation Cell
// ...
- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated
{
[super setHighlighted:highlighted animated:animated];
if (highlighted) {
self.photoTitleLabel.shadowColor = [UIColor darkGrayColor];
self.photoTitleLabel.shadowOffset = CGSizeMake(3, 3);
} else {
self.photoTitleLabel.shadowColor = nil;
}
}
@end
View controller 应该在 model 和 view 对象之间扮演协调者和调解者的角色。它不应该关心明显属于 view 层或 model 层的任务。我们应该始终记住这点,这样 delegate 和 data source 方法会变得更小巧,最多包含一些简单地样板代码。
这不仅减少了 table view controllers 那样的大小和复杂性,而且还把业务逻辑和 view 的逻辑放到了更合适的地方。Controller 层的里里外外的实现细节都被封装成了简单地 API,最终,它变得更加容易理解,也更利于团队协作。
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/abc649395594/article/details/49055553
内容来源于网络,如有侵权,请联系作者删除!