MVC优化-笔记
喵神有2篇文章讲如何优化MVC:
保险人原项目,为了避免部分刷新数据时闪退,统一使用reloaddata,不够高效,造成性能以及无法统计曝光等问题,亟需解决!
方案大致有2个:
-
改成MVVM,使用RxSwift全家桶中的RxDataSource。不过RxSwift上手还真是难。
-
其他方式,比如本文所提到的,改造mvc
关于 MVC 的一个常见的误用
误用和影响
通常我也会误用MVC,保险人项目中到处都是这类的代码,可以看看ToDo.v1.swift代码,误用会导致这些潜在问题:
-
View Controller 持有 model,而且 model 没有任何功能,所有的对 model 的增删改查操作都在 View Controller,另外的数据同步和测试,都会在 View Controller 中进行, 导致 Massive View Controller.
-
违反数据流动规则和单一职责规则
UI 操作不仅导致了 Model 的变更,还同时导致了 UI 的变化。理想化的数据流动应该是单向的:UI 操作 -> 经由 View Controller 进行模型更新 -> 新的模型经由 View Controller 更新 UI -> 等待新的 UI 操作
假设增加一个另一个 View Controller 操作同一份数据,那数据就很难统一管理,还要相互通信,比如其中一个View Controller 删了某一行数据,要通知前一个 View Controller 的列表相应的刷新。
而且很多时候涉及网络请求,代码会散落在各个角落,很难收拾干净。
改善
独立的 Model 管理类 ToDoStore,由 View Controller 持有 ToDoStore
-
由 ToDoStore 来操作 Model 的增删改查
-
ToDoStore 能够以某种“非直接”的方式向 Controller 进行汇报,比如 Notification 或 KVO 或 Block,实现单向数据流动:View Controller 调用 ToDoStore 来操作 Model 的增删改查,ToDoStore 将结果用 Notification 或 KVO 通知 View Controller 更新。( Notification不好,因为在实践会发现需要取很多的通知名;KVO可以,但是需要将数据整理为一个state,不适合多份数据;还是Block最好 )
“完全理解和严格遵守 MVC 的思想,我们其实也可以将 MVC 用得“小而美”。第一步,就从避免文中这类常见“错误”开始吧~”
[单向数据流动的函数式 View Controller
这边文章可以认为是升级版,将独立的 Model 管理类 ToDoStore,通过函数操作 Model 的方式,改为 react 的方式,通过 Action、dispatch等,操作数据流转。有什么区别呢:
- 通过函数操作 Model 的方式,通常是修改某一个数据,而 react 的方式,是把整个APP的数据合并成一个 State。
即独立的 Model 管理类 ToDoStore,适合管理1个数据,而如果扩展,那么diff运算会比较复杂。而 react 的方式适合管理多份数据,根据不同的Action,dispatch到不同的地方执行?
具体实现,还是遵循 React 的 Redux 架构方式,任何操作,都可以归结为 Action,View Controller 持有 Store,并成为订阅者,View Controller 的操作,通过 Store 的 dispatch 方法,调用 reduce 拿到新的 state,再把新的 state 发给订阅者 View Controller,而 View Controller 所需要做的,就是根据新的 state,来刷新自己的状态。但是这里也不涉及diff运算??还不如之前的独立的 Model 管理类 ToDoStore??
反而RxDatasource,通过Differentiator,进行diff运算,得到应该刷新的tableview的位置!!!
- 关于 command
- 是否应该去掉commond
lionhylra • 1年前 我觉得Command“副作用”这部分有点不好理解。 如果说,reducer的职责是解析action, 更新state。 那么对于loadTodos这条action, 我觉得应该直接执行ToDoStore.shared.getToDoItems(completionHandler: {self?.store.dispatch(.addToDos(items: $0))}), 而不是交给stateDidUpdate 方法来执行“副作用”。因为这一步实际上并没有做updateState的事情(或者可以做state.isLoading = true这样的事情), 等网络请求返回了,再做一次addTodos这个action。
onevcat 管理员 lionhylra • 1年前 更多的是从可测性考虑的,当然也有集中副作用的考虑。
reducer 离 store 更近,它其实甚至不需要是 View Controller 的一部分 (例子中不过是恰好把它写成了 View Controller 的变量)。它扮演的是一个黑盒子,我更希望它是真正的纯函数。而 stateDidUpdate 其实本来就有更新 UI 这个“副作用”了,而且习惯上也会是 View Controller 中响应用户事件的部分,所以我觉得抽象一个 Command 出来,可能会是更好的方式。
文中的 demo 显然是简化的版本。在实际使用中,显然我们这里会在发送 load command 的同时设置 isLoading 状态来显示 Indicator,而且从网络过来的 addTodos action 其实更应该是 addTodosFromNetwork action,它需要负责再将 isLoading 置为 false。所以说 Command 不伴随 State 的变化仅仅是示例项目中的一个“特例”。
如果不关心测试的话,确实也可以直接在 reducer 里直接做请求,但是这样的话这部分的测试会变得很困难。提取 Command 的话,通过测试 reducer 可以返回正确的 Command 以及创建一个带有自定义回调的 Command,会让这部分变得可测。
- 是否应该增加更多的commond
minghua wei onevcat • 43分钟前 而且,我觉得应该把更新 UI 这个“副作用”做成command,这样在 stateDidChanged 方法里面,就不用总是调用 tableView的 reloadData 方法,可以 insertRows、deleteRows等等,不知道我的理解对不对
总结
在 XRKVPlanListViewController 尝试了一下 reducer,还是有点别扭,有些疑问得不到好的解答,比如刷新 UI 这个副作用,如何处理,用 command,还是用 diff 运算。也许该尝试一下 RxSwift 了
至少,不能有那么多 load data block 回调,如果是 react 的单向数据流,应该集中起来做数据的 diff 运算和 UI 的刷新,ViewController 里面,只能发 load data action,再接收 data chang 后的 UI 刷新响应,只是怎么接收 data change,怎么 diff,不好设计。如果是RxSwift,则是双向绑定,更好。( ps: RxSwift说是可以做单向数据流的,单向数据流是一种思想 ) – 20181015