第二节、轻量级视图控制器

CSDN博客: forlong401
邮件地址:forlong401@163.com
微博:@forlong401

第一章 轻量级视图控制器,2013年6月,作者:Chris Eidhof

  视图控制器是iOS项目工程中最多的源码文件,它经常存在很多不必要的代码。一直以来,视图控制器是最少被重用的代码部分。我们将探讨瘦身你的视图控制器的技术,让你的代码能重用,并把代码整理到更合适的地方。

  这个话题的示例工程在GitHub的网址:

1.拆分数据源和其它协议
  一种行之有效的瘦身你的视图控制器技术是将视图控制器中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;
}

  这个代码片段中,视图控制器使用数组管理一些特定的图片。让我们来将数组相关的代码重构到特定的类中。我们根据自己的用例和喜好,用一个块或代理来配置单元格。

@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

  有三个方法在你的视图控制器中,你可以创建一个对象实例,再将它设置为表视图的数据源。

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:commitEditingStyle:forRowAtIndexPath:

  并共享这部分代码到所有你表视图控制器中。

  随之来的好事是我们可以单独测试这个类,并永远不必担心重新再写一遍。同样的原则适用于你使用数组以外的其他情形。
  今年我们承担的一个应用中,我们使用了很多核心数据。我们创建了一个类似的类,但不是用的数组,而是获取结果的控制器。它实现了所有的逻辑:更新动画、处理区域标题和删除。你可以创建这个对象的一个实例,然后给它一个获取请求和块来配置单元格,再处理剩下的工作。
  此外,这种方法也可以扩展到其它协议。一个典型的例子就是:UICollectionViewDataSource。如果在相同的开发点上,你决定用UICollectionView替换UITableView,并不得不修改你的试图控制器,这样将给你提供了相当大的灵活性。你甚至可以让你的数据源支持两种协议。

2.重构领域逻辑到模型
  下面是一个视图控制器(来自另外一个工程)的代码片段,它支持查找一个用户的活跃优先级列表:
- (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类的一个分类。然后看起来像在视图Controller.m中所示:
- (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];
}

  一些代码无法轻易重构到一个模型对象,但仍明显与模型相关的代码,这时我们可以使用一个存储。

3.创建存储类
  在我们的示例应用程序的第一个版本中,我们有一些代码是从文件加载数据并解析它。这段代码在视图控制器:
- (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];
}

    
  视图控制器不应该知道这些。我们只是创建了一个存储对象。通过分离出来,我们可以重用这部分代码,单独测试它,使我们的视图控制器更轻巧。存储可以处理数据加载、缓存和建立数据库。这个存储通常也称为服务层或存储库。

4.重构网络服务逻辑到模型层
  这非常类似于上面的话题:不要做网络服务的逻辑在你的视图控制器。而是封装到另一个类。你的视图控制器可以通过一个回调处理程序(例如,完成块)调用这个类上的方法。这样的好处是,你可以在这个类做你所有的缓存和错误处理。

5.视图代码重构到视图层
  不应该在视图控制器中构建复杂的视图层次结构。而是使用界面构建器IB(Interface Builder),或者封装到自己的UIView子类。举个例子,如果你构建自己的日期选择器控件,把这些构建成一个DatePickerView类而不是在视图控制器处理所有事情。同样原理,这增加了可重用性和简单性。

  如果你喜欢界面构建器IB(Interface Builder),那么在界面构建器中也可以这样做。有些人认为你只能使用某个视图控制器,但是你也可以加载您自定义视图的单独nib文件。在我们的示例应用程序,我们创建了一个包含照片的布局单元格的PhotoCell.xib:

photocell

  正如你所看到的,我们创建了视图的属性(我们不使用这个xib文件的所有者对象)并将它们连接到特定中的子视图。这种技术也非常方便其他自定义视图。

6.通信
  另外一件事情是发生在很多视图控制器和其它视图控制器、模型和视图之间通信问题。而这正是一个控制器应该做的,这也是我们想达到的尽可能少的代码愿景。
  有很多良好的技术解释视图控制器和模型对象之间的通信(如KVO和获取结果控制器),然而,视图控制器之间的通信通常是有点不太清晰。

  我们经常遇到一个问题:一个视图控制器的一些状态需要和与多个其他视图控制器通信。通常的做法是把这种状态放到一个单独的对象并将其传递给视图控制器,然后观察和修改状态的所有情况。它的优势是:都是在一个地方,最终我们不用纠缠在嵌套代理回调中。这是一个复杂的问题,我们可能把这个问题在未来某期的话题里深入探讨。

7.总结
  我们已经探讨了一些用于创建更小的视图控制器技术。我们不仅仅是要努力尽可能应用这些技术,而且我们只有一个目标:编写可维护的代码。通过了解这些模式,我们有更好的机会去使笨拙的视图控制器变得更清晰。

8.延伸阅读

没有更多推荐了,返回首页