更轻量的 View Controllers
iew controllers 通常是 iOS 项目中最大的文件,并且它们包含了许多不必要的代码。所以 View controllers 中的代码几乎总是复用率最低的。我们将会看到给 view controllers 瘦身的技术,让代码变得可以复用,以及把代码移动到更合适的地方。
你可以在 Github 上获取关于这个问题的示例项目。
把 Data Source 和其他 Protocols 分离出来
把 UITableViewDataSource
的代码提取出来放到一个单独的类中,是为 view controller 瘦身的强大技术之一。当你多做几次,你就能总结出一些模式,并且创建出可复用的类。
举个例,在示例项目中,有个 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;
}
这些代码基本都是围绕数组做一些事情,更针对地说,是围绕 view controller 所管理的 photos 数组做一些事情。我们可以尝试把数组相关的代码移到单独的类中。我们使用一个 block 来设置 cell,也可以用 delegate 来做这件事,这取决于你的习惯。
@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
现在,你可以把 view controller 中的这 3 个方法去掉了,取而代之,你可以创建一个 ArrayDataSource
类的实例作为 table view 的 data source。
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;
现在你不用担心把一个 index path 映射到数组中的位置了,每次你想把这个数组显示到一个 table view 中时,你都可以复用这些代码。你也可以实现一些额外的方法,比如 tableView:commitEditingStyle:forRowAtIndexPath:
,在 table view controllers 之间共享。
这样的好处在于,你可以单独测试这个类,再也不用写第二遍。该原则同样适用于数组之外的其他对象。
在今年我们做的一个应用里面,我们大量使用了 Core Data。我们创建了相似的类,但和之前使用的数组不一样,它用一个 fetched results controller 来获取数据。它实现了所有动画更新、处理 section headers、删除操作等逻辑。你可以创建这个类的实例,然后赋予一个 fetch request 和用来设置 cell 的 block,剩下的它都会处理,不用你操心了。
此外,这种方法也可以扩展到其他 protocols 上面。最明显的一个就是 UICollectionViewDataSource
。这给了你极大的灵活性;如果,在开发的某个时候,你想用 UICollectionView
代替 UITableView
,你几乎不需要对 view controller 作任何修改。你甚至可以让你的 data source 同时支持这两个协议。
将业务逻辑移到 Model 中
下面是 view controller(来自其他项目)中的示例代码,用来查找一个用户的目前的优先事项的列表:
- (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 中会变得更加清晰,处理之后,在 View Controller.m
中看起来就是这样:
- (void)loadPriorities {
self.priorities = [user currentPriorities];
}
在 User+Extensions.m
中:
- (NSArray*)currentPriorities {
NSDate* now = [NSDate date];
NSString* formatString = @"startDate = %@";
NSPredicate* predicate = [NSPredicate predicateWithFormat:formatString, now, now];
return [[self.priorities filteredSetUsingPredicate:predicate] allObjects];
}
有些代码不能被轻松地移动到 model 对象中,但明显和 model 代码紧密联系,对于这种情况,我们可以使用一个 Store
:
创建 Store 类
在我们第一版的示例程序的中,有些代码去加载文件并解析它。下面就是 view controller 中的代码:
- (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 对象来做这些事。通过分离,我们就可以复用这些代码,单独测试他们,并且让 view controller 保持小巧。Store 对象会关心数据加载、缓存和设置数据栈。它也经常被称为服务层或者仓库。
把网络请求逻辑移到 Model 层
和上面的主题相似:不要在 view controller 中做网络请求的逻辑。取而代之,你应该将它们封装到另一个类中。这样,你的 view controller 就可以在之后通过使用回调(比如一个 completion 的 block)来请求网络了。这样的好处是,缓存和错误控制也可以在这个类里面完成。
把 View 代码移到 View 层
不应该在 view controller 中构建复杂的 view 层次结构。你可以使用 Interface Builder 或者把 views 封装到一个 UIView
子类当中。例如,如果你要创建一个选择日期的控件,把它放到一个名为 DatePickerView
的类中会比把所有的事情都在 view controller 中做好好得多。再一次,这样增加了可复用性并保持了简单。
如果你喜欢 Interface Builder,你也可以在 Interface Builder 中做。有些人认为 IB 只能和 view controllers 一起使用,但事实上你也可以加载单独的 nib 文件到自定义的 view 中。在示例程序中,我们创建了一个 PhotoCell.xib
,包含了 photo cell 的布局:
就像你看到的那样,我们在 view(我们没有在这个 nib 上使用 File's Owner 对象)上面创建了 properties,然后连接到指定的 subviews。这种技术同样适用于其他自定义的 views。
通讯
其他在 view controllers 中经常发生的事是与其他 view controllers,model,和 views 之间进行通讯。这当然是 controller 应该做的,但我们还是希望以尽可能少的代码来完成它。
关于 view controllers 和 model 对象之间的消息传递,已经有很多阐述得很好的技术(比如 KVO 和 fetched results controllers)。但是 view controllers 之间的消息传递稍微就不是那么清晰了。
当一个 view controller 想把某个状态传递给多个其他 view controllers 时,就会出现这样的问题。较好的做法是把状态放到一个单独的对象里,然后把这个对象传递给其它 view controllers,它们观察和修改这个状态。这样的好处是消息传递都在一个地方(被观察的对象)进行,而且我们也不用纠结嵌套的 delegate 回调。这其实是一个复杂的主题,我们可能在未来用一个完整的话题来讨论这个主题。
总结
我们已经看到一些用来创建更小巧的 view controllers 的技术。我们并不是想把这些技术应用到每一个可能的角落,只是我们有一个目标:写可维护的代码。知道这些模式后,我们就更有可能把那些笨重的 view controllers 变得更整洁。
更轻量的 View Controllers的更多相关文章
- Riot - 比 Facebook React 更轻量的 UI 库
Riot 是一个类似 Facebook React 的用户界面库,只有3.5KB,非常轻量.支持IE8+浏览器的自定义标签,虚拟 DOM,语法简洁.Riot 给前端开发人员提供了除 React 和 P ...
- api-hook,更轻量的接口测试工具
前言 在网站的开发过程中,接口联调和测试是至关重要的一环,其直接影响产品的核心价值,而目前也有许多技术方案和工具加持,让我们的开发测试工作更加便捷.接口作为数据传输的重要载体,数据格式和内容具有多样性 ...
- 比Wireshark更轻量、更方便的抓包软件:Charles
转:http://blog.csdn.net/lixing333/article/details/42776187 之前写过一篇通过Wireshark进行抓包,分析网络连接的文章<通过WireS ...
- Crawlab Lite 正式发布,更轻量的爬虫管理平台
Crawlab 是一款基于 Golang 的分布式爬虫管理平台,产品发布已经一年有余,经过开发团队的不断打磨,即将迭代到 v0.5 版本.在这期间我们为 Crawlab 加入了大量社区用户共同期望的功 ...
- 不用webservice wcf提供服务,用Rest更轻量
从2005年开始就开始有基于服务的开发方式,到08年时候 微软和sun等公司都已经提供了很多基于服务的开发框架 . 微软 .net 平台的基于服务的框架主要有:.NET Remoting.webser ...
- kubernets轻量 contain log 日志收集技巧
首先这里要收集的日志是容器的日志,而不是集群状态的日志 要完成的三个点,收集,监控,报警,收集是基础,监控和报警可以基于收集的日志来作,这篇主要实现收集 不想看字的可以直接看代码,一共没几行,尽量用调 ...
- 一种简单,轻量,灵活的C#对象转Json对象的方案
简单,是因为只有一个类 轻量,是因为整个类代码只有300行 灵活,是因为扩展方式只需要继承重写某个方法即可 补充:修正无法处理可空值类型的bug 首先我将这个类称之为JsonBuilder,我希望它以 ...
- 编写轻量ajax组件01-对比webform平台上的各种实现方式
前言 Asp.net WebForm 和 Asp.net MVC(简称MVC) 都是基于Asp.net的web开发框架,两者有很大的区别,其中一个就是MVC更加注重http本质,而WebForm试图屏 ...
- Vue.js:轻量高效的前端组件化方案
转发一篇尤老师对vue.js的介绍,了解vue.js的来龙去脉.不过现在已经是2.0了,也有添加一些新的东西,当然有些东西也改了. Vue.js:轻量高效的前端组件化方案 Vue.js 是我在2014 ...
随机推荐
- linux VM复制多个IP配置出错的处理
device eth0 does not seem to be present, delaying initialization (2012-09-13 21:16:38) 转载▼ 标签: 杂谈 ...
- Web开发入门疑问收集(不定期更新)
bootstrap container和container-fluid的区别 原始链接 container 根据显示设备满足的最小宽度,来决定实际内容宽度,是一个根据设置内容阶梯式响应的布局. 例子: ...
- Laravel 5 服务的注册和使用
首先我们需要创建一个服务的类,比如叫CqhServiceProvider,最简单的方式就是用artisan来帮我们创建 php artisan make:provider CqhServiceProv ...
- HT全矢量化的图形组件设计
HT一直被客户称道的就是其全矢量化的设计特色,矢量相比传统图片好处太多了: 矢量可无级缩放,界面不失真不模糊 描述矢量的文本内容远比图片小得多 目前各种window.devicePixelRatio不 ...
- LVS使用整理(1)
开始安装LVS 1)下载相关软件包 mkdir -p /home/tools/cd /home/tools/wget http://www.linuxvirtualserver.org/softwar ...
- 计算html标签textarea字符长度
今天学习jQuery,做练习计算html标签textarea字符长度,先添加一个视图操作(Action): 创建一个视图,并按下面顺序标记1,2,3进行写html或javascript脚本: 其中标记 ...
- [水煮 ASP.NET Web API2 方法论](3-3)路由默认值
问题 如何为路由中参数设置默认值. 解决方案 不管使用属性路由还是集中式路由,ASP.NET WEB API 都可以很方便的为路由定义默认参数.在每次客户端请求的时候,如果客户端没有传这些参数,框架会 ...
- php实现的IMEI限制的短信验证码发送类
php实现的IMEI限制的短信验证码发送类 <?php class Api_Sms{ const EXPIRE_SEC = 1800; // 过期时间间隔 const RESEND_SEC = ...
- 使用mvc3实现ajax跨域
ajax跨域一般两种方式 1:cors,2:jsonp, 1:cors jsonp是get形式,承载的信息量有限,所以信息量较大时CORS是不二选择 在请求消息头添头 Access-Control ...
- 野比的示波器案例(Winfrom用户控件)
使用该用户控件做的效果图,如果数据正确,可实现 波形.直线.等等效果图...... 对于本程序的认识还是不够深彻.如果有其他方法或算法,欢迎讨论下.将我所能理解的代码都再次标识了一番. ------- ...