ViewController做了太多事情导致变得越来越庞大。键盘事件,用户输入,数据的转换,视图初始化等等,他们真的是ViewController应该做的么?哪些需要委托给其他对象?本文我们就研究下如何让他们各司其职,这可以把大量复杂的代码拆分开,让你的代码更具可读性。
在ViewController中,我们可能是通过#pragma mark来把代码块儿进行分组。如果真的是上述说的这样,那你是该想想如何解耦你的ViewController了。
Data Source(数据源)
数据源模式是隔离对象背后的逻辑代码的一种模式,尤其是在复杂的TableView中,移除ViewController里的大量的比如,“在这种条件下,哪些cell是可见的?” 逻辑代码,是很有用的。如果你的tableview代码里不断的比较[row count]和[section count]的话,那么你可以考虑采用这种模式。
Data Source对象可以遵循UITableViewDataSource协议,但我发现用这些对象来配置cell与管理indexPath,职责并不一样,所以我倾向于把他们分开,各司其职。
一个简单的处理分段逻辑的的例子。
虽然数据源设计成抽象并且可复用,不要担心你的数据源从同一处调用。从ViewController中分离indexPath逻辑是个很崇(diao)高(bao)的目标。尤其是高度动态化的tableView,用一个object来告诉ViewController类似于“hey,我有一个新对象在这个indexPath中”是非常棒的,然后ViewController就能够通过动画的形式来让tableview展示出来。
这种模式也可以封装你的检索逻辑,一个远程的数据源能够从API中获取数据对象集合。然而UIViewController是与UI打交道的控制器,把与UI无关的网络代码从你的ViewController中分离是非常不错的方式。
如果你的所有数据源的接口都是稳定的(例如通过协议),那么你可以写一个特定的数据源,由其他数据源抽象组成的,多个数据源中每个子数据源负责自己的部分。使用这种逻辑来结合数据源是一个很棒的方式,能够避免写出可怕的索引比较的代码。数据源将为你管理一切
Standard Composition
多个视图控制器的组合可以参考iOS5中曾介绍过的View Controller Containment APIs来设计,如果你的view controller由若干个逻辑单元组成,这些逻辑单元拥有他们独立的ViewController,考虑使用Composition来解耦他们。最简单实际立即见分晓的方法就是多tableview或者多collectionview的应用。
|
|
这些subview型的controller,数据源统一,自己维护自己的collectionview。这是一种容易理解也更聪明的实现方式。
Smarter Views
如果你正在把subviews的初始化全放在ViewController里时,你可以考虑下Smart Views来实现。UIViewController默认使用UIView来作为控制器的view属性,但是你可以用你自己的view复写!通过调用-loadView方法来做这件事。
你仅仅需要重新声明一下你的view @property (nonatomic) SKProfileView *view,由于属于自定义UIView,iOS会假设self.view是SKProfileView类型,并正确的处理它,这就是所谓的Covariant return type(没找到中文翻译,大概意思是基类中某个函数在派生类中可以override,并且返回值得是基类中那个函数返回值的子类),这是一种很有效的设计模式。(注意:编译器需要知道你的类继承于UIView,所以请确保你的这个类头文件是import过来的而不是@class这样的前置声明!在Xcode6.3以后你也可以将property声明为dynamic,相应的在实现文件中变为@dynamic)
Presenter
Presenter模式包裹一层model对象,这个model对象可以变换属性来用于显示和传递消息。与总所周知的Presentation Model, the Exhibit pattern, 和 ViewModel异曲同工。
|
|
但是要注意,model对象本身不要被暴露出来。Presenter对于model来说更像是看门大爷,这保证了ViewController不能跨过Presenter来直接访问model。这种架构能够降低依赖关系,因为与SKUser有关系的类会很少,这样修改model对app造成的影响很小。
Binding pattern(绑定模式)
从方法形式上来看,可能会叫-configureView。 当数据变化时,绑定模式会根据这个变化更新view。Cocoa与生俱来,因为KVO能够检测model,KVC能够读取model信息,写入view。Cocoa Bindings是这种模式的AppKit版本。像Reactive-Cocoa的第三方页也是这种模式,但也有可能会大材小用。
这种模式结合Presenter模式配合起来很爽,用一个对象转换值,另一个来响应视图。
|
|
(注意,上面的presenter并不必须支持KVO的,但可以设计成支持KVO)
你不必在一次尝试中就结合上述模式找到完美的架构。也不用区担心只适用于一种模式,目标不是去简单的复用代码,而是是我们的类简单清晰,这样才能易于维护和理解。
交互模式
使ViewController变得臃肿难以维护的另一个原因是简单的一行代码actionSheet.delegate = self。 在Smaltalk语言里,controller所充当的全部的角色仅仅是获取用户输入,响应视图与更新model。如今的交互模式的设计让ViewController重的代码更加的复杂,庞大。
交互设计通常包括用户输入(例如按钮的点击),选择性输入(例如提示框询问用户选择),和一些其他的行为活动,例如网络请求,状态变更等等。这整个的生命周期都可以囊括在Interaction Object里。下面的例子是当点击按钮出发时创建了一个Interaction Object,但是如果你把Interaction Object作为行为的响应(类似这种:[button addTarget:self.followUserInteraction action:@selector(follow)]),也是可行的
|
|
老的alertview和actionsheet的代理模式能够让这种模式更容易展示出优势,但在iOS8UIAlertController APIs下也能玩的很6
Keyboard Manager
当keyboard状态改变更新视图,这是大家在ViewController中要处理的典型的棘手问题,但是如果转换成Keyboard Manager会容易不少。像GitHub上的IQKeyboardManager这种,能够工作在app的任何页面上,但是仍要注意,如果是大材小用,那么你大可以自己写一个符合自身业务的mini版本。
你可以在-viewDidAppear和-viewWillDisappear调用-beginObservingKeyboard和-endObservingKeyboard方法,或者任何合适的地方。
Navigator
屏幕间的切换一般通过-pushViewController:animated:的调用来实现,由于这种切换变得越来越繁琐,你可以委托给Navigator对象来完成。
尤其是在iPad和iPhone通用的app里,navigation可能需要根据你的设备适配。
|
|
上述代码的一个好处是将一个大的对象分解成很多小对象,更容易快速的修改,重写。取代了贯穿你ViewController的条件判断代码,你只需要创建一个self.navigator = [SKiPadUserNavigator new],然后调用相同的-navigateToFollowersForUser:方法即可。
最后
从历史来看,Apple的SDK仅仅包含最小限度的组件,他们的API则是Massive View Controller的元凶。通过分析梳理ViewController的职责,将代码抽象分离出来,创建真正的只有单一职责的对象,这样我们就能够开始把控那些庞大复杂的类,让他们便于管理了。