一般我倾向于更改数据属于 业务逻辑 , ViewController 只应该放 视图逻辑 。二者关系如图:
换句话说就是 ViewController 不需要知道 ViewModel 到底做了什么,只需要满足 ViewModel 具体的输入逻辑,比如:
self.loginButton.rx_tap .bindTo(viewModel.loginButton)
这段代码就是表明了 ViewModel 中的登录事件是通过 loginButton
点击产生的。
这是一个非常好的观点,不暴露 Model ,说明依赖关系是这样的:
事实上我写的时候是暴露的(尴尬):
不过我在设置 View 时是不依赖 Model 的,比如 view.set(title: title)
。
我最先看到的是 BaseViewController
和 BaseTableViewCell
,分别提供了 func setupConstraints()
和 func initialize()
抽象方法,很好,我很喜欢。
之前在 GMTC 有位 ThoughtWorks 的朋友来问我一些实践的问题,其中一个就是 viewDidLoad
中写了很多声明逻辑关系的代码。我想这个是有两种比较好的解决方案的:
func setupViewModel()
之类的方法,将代码分离开,当然具体如何写这样的方法还是要看具体的逻辑。 当然了,记得我们有 // MARK: -
和 // MARK:
帮助我们更好的进行分级。
虽然我们并没有解决代码臃肿的问题,然而这也没什么关系,毕竟这些代码总是要有的,放哪里都是放。阅读起来更方便一些就好了~
我们以 TaskListViewModel
为例解读如何写一个 ViewModel :
// MARK: Input let addButtonDidTap = PublishSubject<Void>() let itemDidSelect = PublishSubject<NSIndexPath>() var itemDeleted = PublishSubject<NSIndexPath>() // MARK: Output let navigationBarTitle: Driver<String?> let sections: Driver<[TaskListSection]> let presentTaskEditViewModel: Driver<TaskEditViewModel>
作者 devxoul 已经标出来了,定义了三个输入行为:
三个输出结果:
我想这并不难理解,如果我们不关心内部实现的话,现在我们就只需要指出对应的输入行为,比如 添加行为 :
self.addBarButtonItem.rx_tap .bindTo(viewModel.addButtonDidTap) .addDisposableTo(self.disposeBag)
接下来就只需要指出对应的展示结果,比如 所有要展示的任务 :
viewModel.sections .drive(self.tableView.rx_itemsWithDataSource(self.dataSource)) .addDisposableTo(self.disposeBag)
至于 ViewModel 的内部实现也不难理解:
self.itemDeleted .subscribeNext { indexPath in let task = tasks.value[indexPath.row] Task.didDelete.onNext(task) } .addDisposableTo(self.disposeBag)
有删除 item 时,就删除 ViewModel 内部的一个 task ,同时告诉 Task
我删除了一个 task 。
Task.didDelete .subscribeNext { task in if let index = self.tasks.value.indexOf(task) { self.tasks.value.removeAtIndex(index) } } .addDisposableTo(self.disposeBag)
事实上,上面这些代码基本上都是在表达 怎么做 ,而不是 做什么 。
当然这个 RxTodo 项目因为加入了 ViewModel ,不方便直接解释,我们换一种形式,对于 添加任务 这个功能的逻辑就是:
点击添加按钮,进入编辑任务信息界面,然后输入任务信息,输入完毕后,根据任务信息添加到当前任务列表。
上面这段话就是 怎么做 ,也就是所谓的声明。
用代码表示大概是这个样子:
// TaskListViewController addButton.rx_tap // 点击添加按钮 .subscribeNext(showTaskEdit) // 进入编辑任务信息界面 // TaskEditViewController ensureButton.rx_tap .withLatestFrom(taskInfo) // 确认输入完毕 .subscribeNext(Task.add) // 添加到当前任务列表
可以看到,我们可以很清楚的用代码表述业务逻辑。
放到初始化大概会是这个样子:
// TaskEditViewModel init(input: ( cancelButtonDidTap: Observable<Void>, doneButtonDidTap: Observable<Void>, alertLeaveButtonDidTap: Observable<Void>, alertStayButtonDidTap: Observable<Void>, memo: Observable<String> ) ) { // ... }
有两个好处:
PublishSubject
还可以作为 Observable
,可以被外界(ViewController)当做输出使用,这一点就比较尴尬了 粗略的写了一下,如有错误或是不合理还请直接指出来~