说实话这本书买了这么久,我都没有好好花时间看完和总结过,所以新冠阳性以后,我决定坚持把它看完,不再留下什么遗憾。这里记录一些看完后的想法和感想,主要总结了以下几个架构:
- MVC
- MVVM+C
- 网络
- MVC+VS
- MAVB
- Elm (TEA)
官方源码下载 https://github.com/objcio/app-architecture
MVC
Model-View-Controller是最常见的架构模式,Model负责数据(更新发通知),View负责视图(事件回调走代理),Controller作为桥梁负责衔接Model和View的交互和交流。
我觉得印象深刻的地方在于,作者即使在MVC的案例中依然保持使用单向数据流,比如删除列表中的文件目录代码实现,只更新了Model的删除,然后通过发送通知的方式来更新列表。这让我觉得在项目中使用一致的数据交互约定进行开发尤为重要,这样即使选择了结构松散和自由度高的MVC架构来开发程序,也能提升代码管理和可读性。这点在我看来是值得学习的精髓。
优点:
- 架构简单,自由度高,苹果认证
- 适合快速上手,开发阻力低
缺点:
- 逻辑耦合程度高,状态越多,越容易出bugs
- 项目逻辑逐渐复杂时,会变成Massive-View-Controller
- 没有严谨的观察者模式,通知和代理容易被滥用
- 不适合单元测试
适用场景:
- 快速迭代,原型开发,需求灵活扩展
MVVM+C
Model-View-ViewModel跟MVC类似,只是ViewModel接替了Controller的工作,由于不同视图相关逻辑被分散至不同的ViewModel中,这样就减少了Controller的负担,使其不再复杂和臃肿。另外还有一个协调器Coordinator负责controller之间的跳转,这种架构使得vc和view、vc之间减少耦合。除此之外,MVVM的最大价值在于引入响应式编程(比如RxSwift),响应式的好处在于让数据流变得清晰可控,你可以通过建立ViewModel和View的绑定关系清楚地看到数据来源和数据转换,从而避免了其他太多选择造成的混乱。
优点:
- 缓解MVC中Controller过于复杂的问题
- 适合响应式编程
- 方便接口测试
缺点:
- 响应式框架学习曲线陡峭
使用场景:
- 业务复杂,MVC开始膨胀的时候
MVVM去Rx版
如果在项目中使用MVVM,但是又觉得引入Rx框架负担太重,也可以选择轻量化的响应式框架或者自己实现。比如KVO或者封装一下Notification,做到既可以实现订阅,又不至于滥用的程度。当然我认为响应式本质是一种数据交互的方式,即使不用框架,也可以自己约定,而框架的用处就是用统一的方式严格去执行这种约定,这就意味着从代码层面可以避免逻辑混乱不清的情况。
网络
关于网络章节部分,主要是讨论网络请求逻辑放在Controller层还是Model层。
网络逻辑放在Controller层是很常见的,加载页面时网络请求数据,删除数据时等待网络请求返回成功后再删除本地数据并刷新UI,这些都是响应网络优先的流程。
网络逻辑在Model层的话,除了让Controller减负以外,还有个原因我理解是弱化网络的存在感。网络在Model层的作用只是用来同步数据而已,这与上面的区别在于:
- Model可以先从本地缓存中获取,而不用等待网络回调
- 网络服务类可以注册本地Model变更的通知,维护一个队列来把变更操作依次入队并同步给服务端
- 离线模式下也允许本地修改Model,等有网络的时候再同步给云端
- 网络更新了Model数据,也会通过Model发送变更通知给订阅者来,保持数据流交互一致
看了网络部分的架构,我觉得是在强调两种理念:云端优先还是本地优先。而作者的观点是,两种架构的不同取决于希望谁拥有数据,Controller还是Model,而谁拥有的关键在于该数据是否希望被共享。
MVC+VS
全称Model-View-Controll + ViewState,特点在于单向数据流和ViewState(书里例子是ViewStateStore,我这里简称VS),它不仅仅是记录view状态的对象,而是扩展成了记录app状态处理逻辑的全局对象,比如app进入到哪个页面,用户执行的播放、删除或保存操作,都会由VS来管理。相对于MVC来说,我觉得这种架构更像是“集权式管理”,虽然每个vc都有自己的model,但是数据变更和流向都是严格上报全局的VS(其实是更新了每个vc自己专属的ViewState),然后再下发通知来更新UI。
优点:
- 单向数据流:相对于MVC,数据的改动不会直接导致vc来更新view,而是借由更新ViewState,通过订阅的方式来从ViewState收到变更通知来更新view。无论是用户触发view的操作,还是网络请求的model更新,都会通过更新ViewState来通知变更,数据流向统一且清晰。
- 订阅:数据的更新是通过订阅ViewState来得到的
- 时光机:因为VS是个全局的状态集,这就意味着每次改动都可以保存一份快照,相当于时光机一样,你可以让app回退到之前某个时刻的状态。
- 调试:相比于时光机,更加实际的应用是调试,因为在当下就可以输出app此时的状态集,甚至历史状态。
- 测试:因为分层和数据流都相对严谨,所以测试方面大多集中在VS,应该比较省心
- 状态恢复:恢复了全局VS相当于恢复了app的所有状态
- vc间通信:由全局VS定义所有跳转action,再由订阅了跳转事件的对象来创建加载vc
缺点:
- 对于体量大、逻辑复杂的app来说,全局的VS应该会过于庞大和繁重,可能需要拆分和调整,或者更换其他架构(个人观点)
- 每个vc的创建都需要全局的VS,耦合性其实挺高的(书里把VS封装成context,作为参数传递给每个vc,也提到可以直接访问单例对象)
- 对于需求多变、快速迭代的要求下,没有MVC灵活。
适用场景:
- 需要状态集中管理的场景
- 数据流动严格统一的场景
- App版Time Machine
- 在任意时刻打断点,都能调试看到app任何vc当下的状态
- 即使不适合用在管理全局状态的话,应该也可以作为app中某项功能的全局状态管理(虽然目前没有尝试过),用混合的方式来搭配其他架构开发app也许会更加灵活
MAVB
ModelAdapter-ViewBinder,其中ModelAdapter体现的是响应式编程,把model数据流转换成信号来输入输出;而ViewBinder体现的是声明+响应式编程,用声明式代码创建布局,然后绑定数据信号。MAVB架构的特点是没有Controller,直接建立Model和View之间的关联,看上去相对轻量一些。
书里的例子用了CwlViews库作为MAVB技术支持,代码风格很极简,我一眼看去感觉很像SwiftUI+Combine的结合。令我印象深刻的是,创建View的时候即绑定数据源的风格,强制让你放弃命令式的思维,这样就避免了在工程里到处维护状态更新,理论上是可以避免程序里绝大部分的bugs。另外CwlViews响应式数据流符号也很直观,也不需要像RxSwift声明DisposeBag来管理生命周期。数据流传递的部分既是重点,也相对复杂,连切换一个播放按钮或者列表里删除某个条目都要围着View->ViewBinder->ModelAdapter->Model->ModelAdapter->ViewBinder->View绕一整圈,不过这也体现了数据流单向和统一的特点。
优点:
- 精简的声明式语法
- 单一数据流
- 支持与MVC混用
缺点:
- 学习曲线陡峭
- 团队维护成本高
- 调试难
适用场景:
- CwlViews不是用来写生产代码的,但是学习CwlViews也许可以更好的帮助我们了解MAVB的理念。
- 对于在项目中使用响应式的话,还是得使用现有的响应式框架,比如ReactiveCocoa, RxSwift, Combine等等。
Elm
The Elm Architecture也简称是TEA,我觉得可以用一句话来描述它:消息-更新-渲染。App在任何地方的任何状态发生变化,其整个状态和model数据将会被传递到一个函数中,他会构建一个新的、不可变的虚拟View层级。框架本身的核心是Driver,通过把虚拟View层级的描述来渲染出实际的UIKit视图,同时也把UIKit事件转化成消息,发送给driver的update函数;框架还把例如弹窗和网络请求等事件封装成了Command对象,给予外部使用。Elm就像一个桥梁一样位于AppState和UIKit之间,你无需接触系统API,而是更新AppState,然后框架会根据app状态来构建虚拟视图层级,再去调用系统API来渲染或创建实际的视图。
与MAVB相比,Elm给我感觉更接近SwiftUI,比如虚拟View的构建更像是ViewBuilder,视图的构建是根据虚拟视图的描述来生成的;而MAVB更加强调的是视图与数据之间的关系,即创建视图的时候就需要定义好数据的流向,从哪里来或到哪里去,充分体现了响应式思想。
优点:
- 声明式构建UI
- 在前端已得到验证
- 减少状态bugs
缺点:
- 对框架封装要求高
适用场景:
- 直接用SwiftUI吧
总结
我看完了整本书,也还没有打算尝试用新架构来重新写点什么,所以暂时无法对它们有深层次的认识。我目前的工作也只是接触过MVC和MVVM,连响应式也很少见到在项目中投入使用。一方面是因为苹果本身推荐MVC,而且对于互联网公司来讲,敏捷开发和快速迭代是符合前期产品发展趋势的,这就无疑在不断巩固着MVC的地位。而学习了其他架构,我发现确实可以开阔视野、增加见识,比如MVC+VS可以创造出时光机的概念,MAVB的数据流向可以如此简洁和优雅,Elm也让我对SwiftUI的实现有了好奇。我觉得书里的某些看法值得提倡,就是这些架构只是工具利器,实际项目中其实很难用单一架构来解决问题,所以其实也可以混合架构,重点在于学习思想和理念,学会分清Model和View,学会用新的思路来解决问题,知道什么场景适合什么架构,而不是无脑完成需求后又得面对数不完的bugs。