姬長信(Redy) 大佬早!

源码

iOS开发 简化view controller


翻译、修改自objc.io

原文链接:Lighter View Controller

导语

view controller通常是一个项目中最庞大的文件,因为它里面经常包含了不属于它的代码,同时这也使它成为代码中最难以重用的部分。所以为view controller瘦身,让其中的代码复用性更强,把相关代码放到正确的地方显得尤其重要。


将Data Source和其他协议分离

为view controller瘦身最有效的方法就是把UITableViewDataSource中的代码移动到相关的类中,具体的方法可以参阅《iOS应用开发 简明TableView》中的相关实现。

而更进一步,不只是TableView,这个方法可以扩展到其他的协议上,比如UICollectionViewDataSource。如果在开发中选择使用UICollectionView代替UITableView时,这个方法可以让你几乎不用修改viewController中的任何东西,甚至可以让Data Source同时支持两个协议,给予了极大的便利性。

将弱业务逻辑移到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];
}

将这些代码移动的根本原因是因为ViewController.m是大部分业务逻辑的载体,本身代码的复杂度已经很高,所以这类跟业务关联不大的代码比如日期转换、图像裁剪、设定过滤器等的操作可以分离到各自的类中完成,一方面为viewController减负,另一方面也能增进代码的复用。

关于这个标题的翻译我斟酌了比较久的时间,因为在原文中是“Move Domain Logic into the Model”,意为“把领域逻辑移到Model中”。对于“领域逻辑”一词我进行过考究,大致意思为“稳定的、不会改变的逻辑关系”,同时在原文中也是使用了NSPredicate作为例子引用,而我认为其例子中的代码也是与业务相关的,只不过关联性不大,而且不会轻易改动,所以使用了“弱业务逻辑”一词代替了“领域逻辑”一词。

把数据处理的逻辑移到服务层

一些代码可能没办法很有效的移动到model中,然而这些代码却和model中的代码有清晰的关联,对于这种问题,可以使用Store。比如在下面的代码中,viewController需要完成从一个文件中获取一些数据,并对其进行操作:

-(void)readArchive 
{
    NSBundle *bundle = [NSBundle bundleForClass:[self class]];
    NSURL *archiveURL = [bundle URLForResource:@"photodata" withExtension:@"bin"];
    NSDate *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 object来完成。

通过对代码进行分离,能够增进代码复用、对代码进行单元测试、保持view controller整洁等。同时能够让view controller更多关注于业务本身的内容,把数据的读取 、缓存、新建等操作交给服务层来处理。

把网络服务的逻辑移到Model层

这与上面提到的十分相似:不要把网络服务的逻辑放到view controller中,而应该把它们存放到不同的类中。

对于view controller,应该只是使用一个completion block来调用这些方法,而把网络请求、错误处理、缓存处理交给这些类来完成

把处理view的代码移到view层

无论是使用Storyboard还是纯代码编写view,创建复杂的view的任务不应该交给view controller。

比如在需要创建一个日期选择器时,更好的方法是把这些代码放到DatePickerView中,而不是放到ViewController中,和之前一样,是为了复用和简便。至于如何使用storyboard对view进行设置这里不再赘述。

Communication(通讯)

view controller中最常发生的就是通讯,包括了和view层的通讯、和model层的通讯、和其他view controller的通讯等。尽管这是一个view controller必须做的事情,这里依然有办法能够对代码进行缩减。

对于view controller和view、view controller和model之间的通讯已经存在大量的优秀经验,比如使用KVO键值模式传值,或者使用Core Data中的NSFetchedResultsController等。然而,对于view controller之间的通讯的相关方法却比较少。

比如在我现在做的项目中,一个view controller需要根据使用者身份的不同(家长/老师)来对view controller的 state 进行不同的设置,而这个 view controller 又在不同的 state 下与不同的 view controller 通讯,传递不同的值。在这种情况下 view controller 中的代码会变得相当臃肿和混乱,所以正确的做法是把这些不同的 state 放到不同的 object 里面,再把它们传给 view controller ,让它们根据这个 state 来进行设置和修改,使我们不再需要被累赘的委托方法搞得十分混乱。

而在另一种情况下,各个 view controller 之间的跳转逻辑十分复杂,存在着严重的横向依赖,在这种情况下就不适宜使用普通的页面跳转模式,而应该使用“中介者模式”,创建一个coordinator controller,让它来管理页面跳转的逻辑。


结论

事实上现在有各种各样的方法为 view controller 减负,它们无一例外都是向着一个目标前进:写出可维护的代码,只有把这些方法灵活运用,熟记于心,才能真正避免弄出笨重而又难以维护的 view controller。


想了解更多内容可以查看我的主页

作者:Dywane

链接:https://www.jianshu.com/p/cdf05c8dc3a5

(0)

本文由 投稿者 创作,文章地址:https://blog.isoyu.com/archives/ioskaifa-jianhuaview-controller.html
采用知识共享署名4.0 国际许可协议进行许可。除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。最后编辑时间为:4 月 24, 2019 at 07:03 下午

热评文章

发表回复

[必填]

我是人?

提交后请等待三秒以免造成未提交成功和重复