//英文不好,好好学习
在IOS项目里,View controller经常是最大的文件,它们通常包含一些非必须的代码。View controller几乎总是代码中最少可以重用的。我们将关注一些技术使你的View controller瘦下来,使代码可以重用,把代码移动到更适当的地方。
分离数据源和其它特性
一个使你的View controller瘦下来非常有效的手段是把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;
}
很多的代码是关于数组的,还有一些只是关于photos由view controller管理。让我尝试着把array相关的代码移动到我们自己的class中。我们使用block去配置cell,但它也有可能是委托,只是根据你自己去选择的。
@implementationArrayDataSource
- (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;
现在,您不用去担心把index path映射到你数组中相应的位置,每当您想在tableview中展示array中的数据,您可以重用这段代码。您也可以实现额外的方法,例如tableView:commitEditingStyle:forRowAtIndexPath:共享这些代码在你的view controller中。
一个好的方面,我们可以单独测试这个类,再也不用担心再写一次,同样的规则你可以应用到其他的东西上不只是array。
一个应用我们让它在这一年工作,我们大量的使用Core Data,我们创建了一个相似的类,但不是依靠一个数组,它依赖一个读取数据的控制器。它实现了所有的动画更新,做节头和删除数据的逻辑。你可以创建一个实例,给它一个获取的数据的请求再用一个block去配置cell。
此外,这种方法也可以扩展到其他的协议。最明显的是UICollectionViewDataSource。这给了你极大的灵活性。如果在开发中的某一刻你决定用UICollectionView替代UITableVIew,你几乎不用改变view controller中的任何东西,你只需要让你的数据支持这两种协议。
把逻辑域移动到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];
}
然而,把他移动到我们类的类别中它将变得更简洁,然后我们将在这个view controller中看到:
- (void)loadPriorities {
self.priorities = [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];
}
一些代码移动到模型中不是那么的简单,但是它明显的跟modol中的代码有关联,因此,我们可以用Store:
创建一个Store类
在我们的第一个实例中,我们一些代码是从文件中加载数据并解析它。这些代码在我们的view controller中:
- (void)readArchive {
NSBundle* bundle = [NSBundle bundleForClass:[selfclass]];
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:[NSArrayclass] forKey:@"users"];
_photos = [unarchiver decodeObjectOfClass:[NSArrayclass] forKey:@"photos"];
[unarchiver finishDecoding];
}
View controller不需要知道这些,我们创建一个Store来做这些。通过把它分离出去,我们可以重用这些代码,分别测试,保持我们view controller简洁。Store可以关注数据加载、缓存、设置数据库堆栈。Store也经常会被服务层或存储调用。
移动网络服务到数据层
这和上面的主题非常相似:不要把网络服务逻辑放在你的view controller。相反,把它封装到不同的类。你的view controler可以调用这个类提供的方法附带一个回调(例如,一个finish block)。你可以把所有处理缓存和错误的放在这个类,这是一件很棒的事情。
移动View代码到View层
创建复杂结构层次的视图不应该放在view controller。你可以用interface builder 或者把views封装到UIView的子类。例如,如果你创建一个日期选择控制器,把它放到一个DatePickerView比直接把所有的东西都在view controller创建更有意义。此外,它可以增加代码的可重用性和使代码简洁。
如果你喜欢用Interface Builder,你可以在Interface Builder中做这些工作。有些人认为你只能把它给View controller用,但是你亦可以加载独立的nib文件与你自定义的视图,在我们的实例app,我们创建一个PhotoCell.Xib,它包含一个photo cell的布局:
正如我们看到的,我们在视图上创建属性,把他们连接到特定的子view,这个技术对于自定义子试图来说非常的方便。
讨论
一个view controller经常会和其它的view controller、数据、视图相互通讯。这正是一个controller应该做的,我们尽可能用最少的代码去实现。
有很多好的技术对于controller和model的通讯(比如KVO和读取结果控制器),然而,controller之间的通讯往往有点儿不大清楚。
我们经常遇到一个问题,一个cotroller有一些状态和其它很多的controller通讯,把它变成一个单独的实例在视图之间传递,所有的controller观察和修改该状态。这样的好处是所有的在一个地方,我们不会陷入一个代理回调。