在iOS项目中,viewController通常是最大的文件, 它们包含的代码通常比必要的多。几乎总是,viewController是代码中最少可重用的部分。我们将研究如何简化viewController,使代码可重用,并将代码移动到更合适的位置。
分离数据源和其他协议
精简viewController的最强大的技术之一是将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;
}
很多代码都与array有关,其中一些与viewController管理的photos有关。因此,让我们尝试将array相关的代码转移到它自己的类中。我们使用一个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
你的ViewController中的三个方法可以执行,你可以创建该对象的实例,并将其设置为tableView的数据源。
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中显示一个数组时,您都可以重用该代码。您还可以实现其他方法,如tableView:commitEditingStyle:forRowAtIndexPath:并在所有表视图控制器中共享该代码。
好处是我们可以单独测试这个类,而不必担心再次编写它。如果你使用的是其他东西而不是数组,同样的原则也适用。
在我们今年工作的一个应用程序中,我们大量使用了Core Data。我们创建了一个类似的类,但是它不是由数组支持的,而是由获取结果controller支持的。它实现了对更新进行动画的所有逻辑,执行部分标题和删除。然后,您可以创建这个对象的一个实例,并为它提供一个获取请求和一个用于配置cell的block,其余的将被处理。
此外,该方法还扩展到其他协议。一个明显的候选者是UICollectionViewDataSource。这给了你极大的灵活性;如果在开发过程中的某个时刻,你决定使用UICollectionView而不是UITableView,你几乎不需要更改viewController中的任何内容。你甚至可以使你的数据源支持这两种协议。
将弱业务逻辑移到Model中
下面是一个视图控制器(来自另一个项目)的代码示例,它应该为用户找到一个活动优先级列表:
- (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];
}
但是,将此代码移动到User类上的类别是非常干净的。然后在viewcontroller.m:
- (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];
}
有些代码不能很容易地移动到模型对象中,但是仍然与模型代码相关联,为此,我们可以使用Store:
创建一个Store类
在我们的示例应用程序的第一个版本中,我们有一些代码来从文件中加载数据并解析它。这个代码在视图控制器中:
- (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];
}
viewController不应该知道这个。我们创建了一个存储对象。通过分离它,我们可以重用该代码,并单独测试它,并保持我们的viewController很小。该存储可以处理数据加载、缓存和设置数据库堆栈。该存储通常也称为服务层或存储库。
将Web服务逻辑移动到Model层
这与上面的主题非常相似:不要在视图控制器中执行网络服务逻辑。相反,将其封装在一个不同的类中。然后,视图控制器可以使用回调处理程序(例如,一个完成块)调用该类上的方法。好处是你可以在这个类中完成所有的缓存和错误处理。
将视图代码移到View层
构建复杂的视图层次结构不应该在ViewController中完成。要么使用interface builder,要么将视图封装到它们自己的UIView子类中。例如,如果你构建自己的日期选择器控件,那么将其放入DatePickerView类比在视图控制器中创建整个事件更有意义。同样,这增加了可重用性和简单性。
如果你喜欢Interface Builder,那么您也可以在Interface Builder中这样做。有些人认为你只能用这个来查看控制器,但是你也可以用你的自定义视图加载单独的nib文件。在我们的示例应用程序中,我们创建了一个PhotoCell.xib包含photo cell的布局:
如你所见,我们在视图上创建了属性(在这个xib中我们不使用文件的所有者对象),并将它们连接到特定的子视图。这种技术对于其他自定义视图也非常方便。
交互
在ViewController中经常发生的另一件事是与其他viewController、Model和View的通信。虽然这正是ViewController应该做的事情,但它也是我们希望尽可能用最少的代码实现的事情。
在你的ViewController和Model对象之间(比如KVO和fetch results controller)之间有很多很好的解耦技术,但是视图控制器之间的通信通常不那么清晰。
我们经常遇到这样的问题:一个ViewController有一些状态,并与多个其他ViewController通信。通常,将这个状态放到一个单独的对象中并将其传递到ViewController,然后所有这些都观察和修改这个状态是有意义的。优点是它都在一个地方,而且我们不会纠缠在嵌套的委托回调中。这是一个复杂的课题,我们将来可能会把整个问题都放在这个问题上。
总结
我们已经看到一些创建较小视图控制器的技术。我们应该尽可能地应用这些技术,因为我们只有一个目标:编写可维护的代码。通过了解这些模式,我们有更好的机会使用笨拙的视图控制器并使它们更清晰。











网友评论