更加轻量级的控制器

 更加轻量级的控制器(Copyright by objc.io) 

  控制器经常是IOS项目中最大的部分,而导致这样的原因是控制器中包含了很多的不必要的代码。控制器代码过重的直接结果是:控制器的代码很难具有重用的功能。我们将讲述一些让你的控制器瘦身,并且让代码更加的可重用和代码能放在合适地方的一些方法。本实例的代码地址https://github.com/objcio/issue-1-lighter-view-controllers

 分离我们的数据源(data source)和代理(delegates)

  让控制器瘦身最快的方法是将代码中关于UITableViewDataSource的方法分离出去,放入自己的类中。如果你多次进行这样的代码分离的工作你就能够熟悉其中模式和分离操作带来的代码重用性。

  例如,在我们的实例程序中有这样代码

# 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数组的,这样做并不好,让我们试着把数组相关的代码移动到它自己的类中去。我们用块代码配置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

  现在你的控制器中的这三个方法可以删除了,取而代之的是你可以创建一个上面这个类的实例,并将这个实例设置为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;

  更爽的事情是你现在不需担心indexpath和数组之间的映射关系了,因为这个操作在数组的类中进行了,并且得到了屏蔽。下次你想要再次创建一个新的tableview来展示数组中的数据的时候直接重用这些代码就可以了。当然,在那个数组处理的类中可以再添加其他的方法来进行重用。

  还有一件更好的事情是:我们可以分开进行测试了!而且不用再担心以后要写以前写过的代码。而且这样的分离代码的方法完全可以应用到除了面对数组之外的其他情况中去。

  另外,这种数据的处理也方便了代理协议的变化,其中一种情况就是UICollectionViewDataSource,例如当项目进行到一定的阶段的时候,项目组决定使用UICollectionView取代tableview,这种情况下几乎不需要改变控制器的代码。你可以让数据源支持两种协议,so easy?

  

  将与模型的逻辑处理放到模型层中去

   这里有一个控制器的代码实例,作用是取得用户的一些活动信息

  

- (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类的一个分类中去,那么控制器将会变得非常轻松: 

- (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];
}

  有些代码不能被简单的移动到模型对象中去,但是依然是和模型对象紧密关联的,这种时候我们就可以使用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];
}

  控制器是不应该知道这些事情的,所以使用Store对象来操作,通过将功能分离我们可以让代码更具有重用性,而且让控制器更加的轻松,专心去处理其他的本该属于它的事情。这个对象就完全处理了数据的加载、缓存和数据栈的操作,而且这个对象会被服务层或者网络经常访问,调用。

 将视图层的代码移动到视图层 

 创建复杂的视图结构不能放在控制器中进行,不论是使用xib或者其他的方式。例如你创建了自己的picker control,把这个过程放在属于它自己的类中进行,明显比放在控制器中进行这个响度复杂的操作要显得轻松,同样的这么做的话,又再一次增加了代码的重用性和简便性。

  如果你习惯使用xib,那么在xib中进行这项操作完全是可以的,但是很不推荐在使用xib进行项目的开发。

 通讯

  控制器需要关注的还有另外很重要的一件事,处理和其他控制器、模型、视图的交流工作。虽然这是控制器的本职工作,但是我们仍然希望通过最少的代码来达到这个目的。

  有很多的方法来进行控制器和模型之间的交互(such as KVO and fetched results controllers), 控制器之间的交流往往还要更加的清晰。

  通常我们会有这样的问题:一个控制器的状态需要通知其他的控制器,通常的解决办法是将这个状态包装到一个对象中进而传递给其他的控制器,这个对象保持并且不断的随着控制器的改变而改变状态,这种做法的优点是,处理的参数只存在这个对象中,这样不用处理过多的代理方法(如果单独使用属性通知控制器就需要单独处理代理对象)。 

 结论

  我们已尽罗列了很对的让控制器瘦身的方法,我们应该还不保留的使用这些方法来进行项目的开发,这样我们才能达到写最少的代码来完成工作的目的,遵守这些协议我们将让控制器变得十分的干净。

  

posted @ 2014-05-16 21:11  一壶浊酒  阅读(120)  评论(0)    收藏  举报