资讯专栏INFORMATION COLUMN

ViewController规范化写法

bitkylin / 1531人阅读

摘要:在文章里提出了几个应该属于规范化问题的概念。除了以上好处之外,我们还可以针对这部分实现编写多带带的单元测试,而且再也不需要到处复制粘贴了。

1、ViewController代码布局

在一个规范的开发团队中,在代码规范上应该达到每一个程序员所写出来的ViewController结构一致的目标,这样的规范能减少各种delegate getter随机出现,ViewController lifecycle方法到处都是。制定一个规范,可以使代码更有利于阅读和维护,当然,规范的制定和团队的架构师的经验而定。
在OS应用架构谈 view层的组织和调用方案这篇文章里提供一个布局规范,如下图所示:

此外,所有需要初始化的属性可以放在getter方法中。

#pragma mark - life cycle
- (void)viewDidLoad
{
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    [self.view addSubview:self.firstTableView];
    [self.view addSubview:self.secondTableView];
    [self.view addSubview:self.firstFilterLabel];
    [self.view addSubview:self.secondFilterLabel];
    [self.view addSubview:self.cleanButton];
    [self.view addSubview:self.originImageView];
    [self.view addSubview:self.processedImageView];
    [self.view addSubview:self.activityIndicator];
    [self.view addSubview:self.takeImageButton];
}

避免出现以下这种情况

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.textLabel = [[UILabel alloc] init];
    self.textLabel.textColor = [UIColor blackColor];
    self.textLabel ... ...
    self.textLabel ... ...
    self.textLabel ... ...
    [self.view addSubview:self.textLabel];
}

一般情况下,View的创建是应该放在View中处理。这两种初始化方式相比,很明显第一种比较简洁,将初始化放到getter中,分工明确,也有利于做测试。

2、轻量化ViewController

在ios的开发中,MVC的核心就在ViewController,有些开发人员在不熟悉MVC的情况下,把所有东西统统放入C里面就可以了,所以往往一个ViewController既有View的创建,也会有Model里的业务逻辑。这样一个臃肿的ViewController不好阅读,不好维护,更不利于做单元测试。
在Lighter View Controllers文章里提出了几个应该属于ViewController规范化问题的概念。

2.1、将DataSource和其他Protocols隔离

精简 ViewController 的有效方法之一就是实现 UITableViewDataSource 协议相关的代码封装成一个类(比如本文中的 ArraryDataSource )。如果你经常在 UIViewController 中实现 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;
}

可以看到上面方法的实现都与 NSArray 有关,还有一个方法的实现与 Photo 有关(Photo 与 Cell 呈一一对应关系)。下面让我们来把与 NSArray 相关的代码从 ViewController 中抽离出来,并改用 block 来设置 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

现在你可以把 ViewController 中的相关方法移除,并且把 ViewController 的 dataSource 设置为 ArrayDataSource 的实例。

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;

通过上面的方法,你就可以把设置 Cell 视图的工作从 ViewController 中抽离出来。现在你不需要再关心indexPath如何与 NSArrary 中的元素如何关联,当你需要将数组中的元素在其它 UITableView 中展示时你可以重用以上代码。你也可以在 ArrayDataSource 中实现更多的方法,比如tableView:commitEditingStyle:forRowAtIndexPath:

除了以上好处之外,我们还可以针对这部分实现编写多带带的单元测试,而且再也不需要到处复制粘贴了。当你使用其他数据容器时,你可以用类似的方式来达到代码复用的效果。

该技巧同样适用于其他 Protocol ,比如 UICollectionViewDataSource 。通过该协议,你可以定义出各种各样的 UICollectionViewCell 。假如有一天,你需要在代码在使用到 UICollectionView 来替代当前的 UITableView,你只需要修改几行 ViewController 中的代码即可完成替换。你甚至能够让你的 DataSource 类同时实现 UICollectionViewDataSource 协议和 UITableViewDataSource 协议。

除此之外,还有很多关于这个方面的讨论:
UITableview代理方法与Viewcontroller分离
不要把 ViewController 变成处理 tableView 的"垃圾桶"

2.2、将业务逻辑移至Model层

下面的示例代码(另外一个工程)位于view controller,作用是找出针对用户active priority的一个列表。

- (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类的一个category中,会让代码更加清晰。此时,在View 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];
}

实际开发中,有一些代码很难将其移至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: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]; 
} 

实际上,view controller不应该关心这些事情的。在示例工程中,我创建了一个Store类来做这些事情——通过将这些代码从view controller中剥离出来,不仅可以对其重用和多带带测试,另外还能对view controller瘦身。Store类专注于数据的加载、缓存,以及对数据库进行配置。这里的Store也经常叫做service layer或者repository。

2.3、其他轻量化规范

将网络请求相关逻辑放到Model层处理

View的创建放到View层处理

3、采用比较清晰的架构

如何给UIViewController瘦身这篇文章非常详细的介绍了臃肿的VC会导致什么问题的出现,同时也提出一个非常清晰的架构图,如下:

4、总结

只有开发人员在完全遵守架构规范进行开发时,架构规范的优势才能完全体现出来。评判一个好的规范的标准很简单,按照规范写出来的代码是否易于阅读、维护、扩展、测试。往往一个成熟的代码规范不是一蹴而就的,需要在实际的开发过程中,不断的去修改完善。但是最重要的一点,适合自己的才是最好的!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/16347.html

相关文章

  • iOS知识梳理 - Objective-C的@property、@synthesize和@dynam

    摘要:注意,很多人误认为默认生成的成员变量是,据我所知这是不对的。官方文档指定也可以指定一个成员变量作为其的目标。的作用是告诉编译器,此属性对应的方法将会被动态提供。 objc推荐我们通过set/get方法访问对象的属性。很显然,为每一个属性手动添加set/get方法的声明和实现是个性价比很低的重复劳动。因此,objc提供了一些关键字帮助我们简化这一过程。实际上就是这么回事儿。 @prope...

    AdolphLWQ 评论0 收藏0
  • iOS代码规范之驼峰命名法camelCase

    摘要:这样的命名方式可以增加代码的可读性这里为大家介绍一下变量方法命名在命名变量方法时,第一个单词的首字母应当小写如类名在命名类时,所有单词的首字母都应当大写如 介绍 每种编程语言都有其特别的变量/方法,C/C++中,很多人喜欢使用大写首字母加下划线的命名方式,在iOS开发中,camelCase命名法是相对流行的方法,苹果的官方提供的所有代码也都符合camelCase命名法它之所以被叫做骆驼...

    laznrbfe 评论0 收藏0
  • 聊聊 iOS Designated Initializer(指定初始化函数)

    摘要:五当遇到指定初始化函数对了还有一个特例苹果官方文档中明确规定翻译一下如父类没有实现协议,那么应该调用父类的指定初始化函数。 聊聊 iOS Designated Initializer(指定初始化函数) 一、iOS的对象创建和初始化 iOS 中对象的使用时分两步完成: 分配内存 初始化对象的成员变量 对应着我们最常见的创建对象过程,如下图: showImg(http://image...

    lvzishen 评论0 收藏0
  • iOS系统中导航栏的转场解决方案与最佳实践

    摘要:背景目前,开源社区和业界内已经存在一些导航栏转场的解决方案,但对于历史包袱沉重的美团而言,这些解决方案并不完美。中的导航栏属于各个业务方的公用资源,由于缺乏相应的约束机制和最佳实践,导致业务方之间的代码耦合程度不断增加。 背景 目前,开源社区和业界内已经存在一些 iOS 导航栏转场的解决方案,但对于历史包袱沉重的美团 App 而言,这些解决方案并不完美。有的方案不能满足复杂的页面跳转场...

    wangbjun 评论0 收藏0
  • ARC 模式下的循环引用引起内存泄漏

    摘要:因为强引用,的引用计数永远不会减为,当原本的强引用对象被释放以后,和成为了一个相互引用的孤岛,永远不会被释放了,这就会引起内存泄漏。在上面的例子中,就是一种非常普遍的引用循环情况,加入如上代码的在或者以后,并不会执行方法,证明内存泄漏了。 自从iOS 5时代自动引用计数(Automatic Reference Counting)技术发布,Cocoa工程师们才扔下了内存管理的包袱,从此在...

    李增田 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<