更轻量的 View Controllers
原文链接:http://objccn.io/issue-1-1/
View 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*)tableViewnumberOfRowsInSection:(NSInteger)section {return photos.count;
}- (UITableViewCell*)tableView:(UITableView*)tableViewcellForRowAtIndexPath:(NSIndexPath*)indexPath {PhotoCell* cell = [tableView dequeueReusableCellWithIdentifier:PhotoCellIdentifierforIndexPath: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*)tableViewnumberOfRowsInSection:(NSInteger)section {return items.count;
}- (UITableViewCell*)tableView:(UITableView*)tableViewcellForRowAtIndexPath:(NSIndexPath*)indexPath {id cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifierforIndexPath: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:archiveURLoptions:0error: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 变得更整洁。
相关文章:

Alpha阶段项目总结
一,设想和目标 1. 我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述? 针对铁大校园,解决旧物堆积、资源浪费的问题。我们的定义很清楚,对于典型用户和场景有过清晰的描述。…

如何成为软件工程师的团队合作者
In my first software engineering role at an eCommerce brand, I often secretly worked on tasks outside of my core responsibilities. And many times I felt isolated from my teammates. 在电子商务品牌的第一个软件工程职位中,我经常秘密地从事核心职责以外…

HTML转义字符
转自地址:http://blog.csdn.net/wusuopubupt/article/details/8817826 by wusuopubupt No.文字表記10進表記16進表記文字 Comment001""""" quotation mark APL quote002&&"&" ampersand003<&l…

制作模拟器和真机通用静态库
通常在项目中使用静态库的时候都会有两个版本,一个用于模拟器,一个用于真机,因为Mac和iPhone的CPU不同,才造成了这种情况。 为了模拟器与真机之间切换调试的方便,制作通用版本非常有必要。现在有两个版本的静态库libSQ…

使用静态URL片段 URL路由 精通ASP-NET-MVC-5-弗瑞曼
转载于:https://www.cnblogs.com/ganmk--jy/p/5570718.html

sql算术运算符_SQL运算符教程–按位,比较,算术和逻辑运算符查询示例
sql算术运算符At its core, the internet and all its applications are just data.互联网及其所有应用程序的核心只是数据。 Every email, tweet, selfie, bank transaction, and more is just data sitting in a database somewhere.每封电子邮件,推文࿰…

python 之socket 网络编程
socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求。 socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,…

(转)使用 Spring缓存抽象 支持 EhCache 和 Redis 混合部署
背景:最近项目组在开发本地缓存,其中用到了redis和ehcache,但是在使用注解过程中发现两者会出现冲突,这里给出解决两者冲突的具体方案。 spring-ehcache.xml配置: <?xml version"1.0" encoding"UTF…

终端SVN常用命令
svn help 帮助 svn checkout path 从服务器checkout文件到本地(path是服务器上的目录,简写svn co path) svn add file_name 往代码库添加新的文件 svn commit -m "xxx" 提交添加的文件,或者本地做的修改到服务器端(“xxx”内为提交说明…

程序员怎么赚更多的钱_自由职业技巧:如何感到更加自信和赚更多钱
程序员怎么赚更多的钱Over my 10 years as a freelance developer, many fellow freelancers have asked me for advice. How can they make freelancing work for them?在我作为自由开发者的10年中,许多自由职业者都向我寻求建议。 他们如何让他们从事自由职业&am…

RedHat 7.0及CentOS 7.0禁止Ping的三种方法
作者:荒原之梦原文链接:http://zhaokaifeng.com/?p538前言: “Ping”属于ICMP协议(即“Internet控制报文协议”),而ICMP协议是TCP/IP协议的一个子协议,工作在网际层。ICMP协议主要用于传输网络…

关于sql 增删改
1.更改数据库的名称 --更改数据库的名称,逗号前面是之前的,后面是改成的名 sp_renamedb student,xuesheng 2.表中有数据的情况下再添加列、删除列 --后来添加列,只能默认可以为空值 altear table shuiguo add [int] varchar(10) --int加上中括…

使用version遇到的那些坑
公司代码管理使用的SVN, 所以就用到了SVN工具version 公司没给买正版的version, 遇到各种崩溃, 各种坑 1. 更新项目时遇到网络不稳定的情况, 更新失败, 项目中的某个文件就莫名其妙的被锁定了 !!! 如果只是更新一个文件还好说, unlock一下就好了,但是如果你是一个文件夹全部…

docker手册_Docker手册
docker手册The concept of containerization itself is pretty old, but the emergence of the Docker Engine in 2013 has made it much easier to containerize your applications. 容器化本身的概念还很老,但是Docker Engine在2013年的出现使容器化应用程序变得更…

MongoDB修改器的使用1
为什么要使用修改器? 通常我们只会修改文档的一部分,这时候更新整个文档就显得很麻烦,通常是通过原子性的更新修改器来完成。 1."$set"修改器 "$set"用来指定某个字段,如果不存在,则创建。这对部…

4GL之Non-SCROLLING CURSOR
在4gl中CURSOR可以说是每一个程序中都会有的,而CURSOR又分为三种SCROLLING CURSOR、Non-SCROLLING CURSOR、LOCKING CURSOR。 Non-SCROLLING CURSOR的聲明有兩種,一種是先定義好sql語句到一個變量里: DECLARE cursor名 CURSOR FROM 變量…
项目总结三--波纹视图
波纹视图的使用 代码在github:https://github.com/wyon0313/YGMoireAnimation

vlookup示例_VLOOKUP示例–如何在Excel中执行VLOOKUP
vlookup示例Microsoft Excel includes a variety of different functions that help users with calculations of any kind. The functionality of Excel is so comprehensive that average users dont even take advantage of most utilities.Microsoft Excel包括各种不同的功…

MySQL--从库启动复制报错1236
链接:http://blog.csdn.net/yumushui/article/details/42742461 今天在搭建一个MySQL master-slave集群时,执行了change master命令,然后又 start slave 启动slave服务,结果查看salve状态就出现错误了: mysql> show slave stat…

使用Script元素发送JSONP请求
// 根据指定URL发送一个JSONP请求 //然后把解析得到的相应数据传递给回调函数 //在URL中添加一个名为jsonp的查询参数,用于指定该请求的回调函数的名称 function getJSONP(url, callback){//为本次请求创建一个唯一的回调函数名称var cbnum "cb"getJSONP.counter;va…

iOS 崩溃记录
dyld: Library not loaded: /System/Library/Frameworks/UserNotifications.framework/UserNotifications Referenced from: /var/containers/Bundle/Application/AEECAAFB-F14A-43AA-9FB8-8388CAC40122/DouLiao.app/DouLiao Reason: image not found 原因应该是iOS系统版本太…

以太坊Geth几种同步模式
链客,专为开发者而生,有问必答! 此文章来自链客区块链技术问答社区,未经允许拒绝转载。 以太坊Geth几种同步模式 同步模式分类 –fast Enable fast syncing through state downloads –light Enable light client mode –s…

[转]Membership 到 .NET4.5 之 ASP.NET Identity
本文转自:http://www.cnblogs.com/jesse2013/p/membership-part3.html 我们前面已经讨论过了如何在一个网站中集成最基本的Membership功能,然后深入学习了Membership的架构设计。正所谓从实践从来,到实践从去,在我们把Membership的…

js填充select下拉框并选择默认值
/* 使用json数组填充下拉框并复选 *//* 初始化下拉框数据 */ var jsonStr { "data": [] }; for (var str in JsonStr.data) {jsonStr.data.push({ "value": JsonStr.data[str].value, "text": JsonStr.data[str].text }); }/* 调用BandSelectOb…

关于curl使用记录
因经常需要排除线上用户问题,查看用户数据请求结果,使用到curl命令,但是总是忘记,在此做下记录。 curl post请求命令行如下: curl -d "param0value0¶m1value1" "url"

智能合约部署及调用
链客,专为开发者而生,有问必答! 此文章来自链客区块链技术问答社区,未经允许拒绝转载。 智能合约部署及调用 以太坊区块链技术2.0版本对于行业应用的开发最主要特性就是实现了智能合约,本质上讲智能合约是由事件驱…

POP到指定的界面
int index (int)[[self.navigationController viewControllers]indexOfObject:self]; [self.navigationController popToViewController:[self.navigationController.viewControllers objectAtIndex:(index -2)] animated:YES];

js markdown chart flow
http://knsv.github.io/mermaid/#example-of-a-marked-renderer转载于:https://www.cnblogs.com/studyNT/p/5584399.html

使用Remix编译和部署以太坊智能合约
链客,专为开发者而生,有问必答! 此文章来自链客区块链技术问答社区,未经允许拒绝转载。 使用Remix编译和部署以太坊智能合约 Remix 是一个开源的 Solidity 智能合约开发环境,提供基本的编译、部署至本地或测试网络…

Java之Array(数组)说明
代码说明: 1 package array;2 3 import java.util.ArrayList;4 import java.util.Arrays;5 import java.util.List;6 7 /**8 * Array使用说明:9 * 内容: 10 * 1、Array实例化; 11 * 2、Array与ArrayList转换; 12 …