iOS 设计模式 - MVC, MVP, MVVM,VIPER
Original - MVC
Apple 可能考虑过不将 View 与 Model 紧密绑定,因为移动应用程序中的View 可以高度重用。
如果保持紧密耦合,我们每次都需要为不同的Model重新实现View。
Model 和 View相关独立 是 Apple MVC 背后的实际想法。
Apple-MVC
View
View负责向用户展示UI界面,View会通过UI界面向Controller传递一个Action,比如用户在屏幕上点击了一个按钮。
Model
Model是要在View组件中显示的数据。
Controller
Controller 的职责主要是从 View 接收这些Action并对其进行处理,Controller 在处理时可能会更新Model。
有时此更新过程需要一段时间,Model 在完成更新过程时通知Controller,Controller 通知 View 使用更新的数据更新其用户界面。
MVC的问题:
1、MVC不是单一职责模式,对于MVC,Controller只是View 和 Model 的中介,View 和 Model 不会直接相互通信,他们通信是经过 Controller,Controller 是它们之间的桥梁,Controller 拥有一个 View 并将一个 Model 与之关联。这会强制Controller 处理大量的职责,一个单独的View会由多个 功能子视图组成,屏幕 Controller需要集成几个不同的复杂模块,Controller 要负责管理多个功能subView 和集成不同的模块。
2、打破了单一职责原则, Controller与View的生命周期紧密相连,测试控制器变得非常艰难,MVC 很难使用测试用例做单元测试。
3、Controller 将管理这些View的生命周期,处理用户交互,在基于客户端-服务器的应用程序中,可能需要进行服务端调用,处理服务器响应,处理错误,运行屏幕刷新计时器、处理多个回调、监听到通知、处理网络请求、数据解析,处理视图的方向等等。这使得控制器的代码更大。显然,代码变得非常难以管理和理解。在 iOS 中引入子控制器的概念后,这个问题稍微得到了解决。
总结:
对于一些UI界面比较简单只需要处理很少职责的的情况下MVC可以很好的工作,对于一些复杂的UI交互或者业务逻辑时,MVC将会很难处理。
MVP (Model-View-Presenter)
View
包括View 和 ViewController,以及所有的UI设置 和 事件处理。
Presenter
presenter 将负责所有的逻辑,包括响应用户的操作和更新UI(通过Delegate),最重要的是 presenter 和UIKit是隔离的,因此容易测试。
Model
Model是要在View组件中显示的数据。
MVP解决了什么问题
MVP 只是 Apple 的 MVC 的扩展,其中Controller把职责分给了 View 和 Presenter。
也是实现了View 和Model的解耦,在View(ViewController)里面实现View和Presenter的绑定,业务逻辑的处理放在Presenter中处理。
它确实将View Controller 视为View,意味着View Controller只包括与View相关的代码,所有的逻辑实现都会放在Presenter。
View 负责 UI 处理,而 Presenter 处理实际的业务逻辑(更新Model和其他事情)。
优点:
在某种程度上,它优于 Apple 的 MVC,它很好地分配了责任,并且这样单元测试就更容易了,因为所有的单元测试用例都写在没有视图相关处理的 Presenter 上。
缺点:
它降低了开发速度,但因为实现 Presenter 并在各个层之间绑定它会带来一些额外的工作。
Presenter 有太多的逻辑需要处理,并没有遵守单一职责原则来做。
以下内容为MVPExample项目部分代码
MVP在代码层面的实现
1、在Presenter 声明一个协议,然后声明一个delegate,用于把处理完的数据回调给View
2、View(ViewController)遵守TrafficLightViewDelegate协议,实现代理方法,在View中持有一个Presenter对象,并且设置Presenter对象的代理为当前View,调用Presenter对象的方法进行逻辑处理
3、在Presenter对象中把处理完的数据回调给View
Presenter代码实现
protocol TrafficLightViewDelegate: NSObjectProtocol {
func displayTrafficLight(description:(String))
}
class TrafficLightPresenter {
private let trafficLightService:TrafficLightService
weak private var trafficLightViewDelegate : TrafficLightViewDelegate?
init(trafficLightService:TrafficLightService){
self.trafficLightService = trafficLightService
}
func setViewDelegate(trafficLightViewDelegate:TrafficLightViewDelegate?){
self.trafficLightViewDelegate = trafficLightViewDelegate
}
func trafficLightColorSelected(colorName:(String)){
trafficLightService.getTrafficLight(colorName: colorName) { [weak self] traficLight in
if let traficLight = traficLight {
self?.trafficLightViewDelegate?.displayTrafficLight(description: traficLight.description)
}
}
}
}
View代码实现
class TrafficLightViewController: UIViewController, TrafficLightViewDelegate {
@IBOutlet weak var descriptionLabel: UILabel!
private let trafficLightPresenter = TrafficLightPresenter(trafficLightService: TrafficLightService())
override func viewDidLoad() {
super.viewDidLoad()
trafficLightPresenter.setViewDelegate(trafficLightViewDelegate: self)
}
@IBAction func redLightAction(_ sender: Any) {
trafficLightPresenter.trafficLightColorSelected(colorName:"Red")
}
@IBAction func yellowLightAction(_ sender: Any) {
trafficLightPresenter.trafficLightColorSelected(colorName:"Yellow")
}
@IBAction func greenLightAction(_ sender: Any) {
trafficLightPresenter.trafficLightColorSelected(colorName:"Green")
}
func displayTrafficLight(description:(String)) {
descriptionLabel.text = description
}
}
Model代码实现
struct TrafficLight {
let colorName: String
let description: String
}
数据服务类,主要用于在App中的网络服务,返回App所需要的Model
class TrafficLightService {
func getTrafficLight(colorName:(String), callBack:(TrafficLight?) -> Void) {
let trafficLights = [TrafficLight(colorName: "Red", description: "Stop"),
TrafficLight(colorName: "Green", description: "Go"),
TrafficLight(colorName: "Yellow", description: "About to change to red")
]
if let foundTrafficLight = trafficLights.first(where: {$0.colorName == colorName}) {
callBack(foundTrafficLight)
} else {
callBack(nil)
}
}
}
References :
MVVM (Model-View-View Model)
Model
Model是要在View组件中显示的数据。
View
包括View 和 ViewController,以及所有的UI设置 和 事件处理。
ViewModel
通过View上的用户操作更新Model 、Model发生改变时通知View去更新界面。
什么是MVVM
1、MVVM 要求将职责分配给 View(视图/视图控制器)、Model 和 View Model。与 MVP 一样,MVVM 也将视图控制器视为视图的一部分。
2、ViewModel通过与View绑定共享更新,想要在iOS中实现绑定并没有原生的方法,可以通过KVO、Delegate、Notification来实现绑定机制,为了更好的实现绑定,开发人员可以将MVVM与响应式方法链接起来。MVVM能够帮助分配职责,响应式方法能够在View 和ViewModel之间做绑定。
3、ViewModel 数据处理的一个小例子可能是,Model 可能具有以毫秒为单位的时间戳,但 View 希望以用户友好的方式表示它。在这种情况下,ViewModel 将负责将时间戳(毫秒)转换为格式化的日期字符串。然后 View 使用此格式化的日期字符串向用户显示。ViewModel 本身保持了代表性数据创建的复杂性,从而减少了View(ViewController)的责任。
MVVM相比MVP解决了什么问题
1、我们已经从 MVP 模式中了解了 View 和 Model 的职责。 ViewModel 是这里的新组件,负责非 UI 相关的处理。在责任分担方面,它可能看起来像 MVP 中的 Presenter。
2、在 MVP 中,View 特定的数据创建逻辑保留在 View 中。 Presenter 只是充当 View 和 Model 之间的中介,并与视图控制器分担一些责任。
3、由于分配不当,ViewController 在 MVP 中仍然需要处理很多责任。然而,在 MVVM 中ViewModel ,顾名思义是模型的视图特定表示。 View 拥有一个 ViewModel,ViewModel 又拥有一个Model 。因此,MVVM 中的一个典型流程是,View 从服务器或数据库获取数据,并将其提供给 ViewModel,ViewModel 然后处理它并通过绑定通知回来。
对于网络请求应该属于哪一部分呢?属于 View 还是 ViewModel ?
1、最初的MVVM说网络请求应该属于View,是有一定道理的,因为网络请求错误的场景可以直接由View处理,还可以由View向用户显示错误或者重试的操作。
2、如果ViewModel想要保留网络请求的代码,使它从View中抽象出来,发生错误时想要用View展示就需要添加额外的抽象层。
优点
尽管View 和ViewModel之间存在耦合,但是可以保持View 和 Model之间的分离,这是Apple 高度要求的,另外从ViewControllrt把业务逻辑分离出来(让ViewModel处理)也会让单元测试更加容易。
缺点
MVVM是很好的架构并且有很好的实践,它的缺点是响应式编程有一些复杂,有很高的学习成本,容易出错开发者也很难发现问题,对于简单的UI界面、业务逻辑不复杂的场景它并不是最好的选择。
References :
www.raywenderlich.com/6733535-mvv…
以下内容MVVMFromMVC项目分析
ViewModel的职责有以下3个方面:
- Model inputs: 拿到View的操作去更新Model
- Model outputs: 把Model的输出传回到ViewController
- Formatting:格式化Model的数据去显示在ViewController上
注意事项
1、在MVVM中,ViewController不会去调用任何的服务,也不会操作任何Model,这些职责都给了ViewModel。
2、把地理编码和天气服务的代码从WeatherViewController 移到 WeatherViewModel中,然后在WeatherViewController中把View绑定到ViewModel属性上。
3、在ViewModel 只添加一个UIKit.UIImage ,不要在ViewModel 里面添加其他的UIKit的类型,正常来说不要导入 UIKit 在ViewModel中
4、把涉及到网络请求的,日期格式化的、温度显示的代码都从ViewController 里面移到 ViewModel 里面
5、最终就是在ViewController里面需要显示的控件都绑定到ViewModel属性上。
在重构MVVM的过程中,我们发现:
好处:
1、ViewController 变得更简单了 、 ViewController 只关注View 不在依赖Model
2、ViewController 只和 ViewModel 交互,通过发送一些输入,比如点击事件 changeLocation(to:) 或者 在ViewController 让ViewModel 和View绑定
3、分离了业务逻辑和View的实现
4、只需要对ViewController 添加很少的代码就可以添加新功能
5、ViewModel 很容易测试
代价:
1、App 需要增加一个新的 ViewModel 结构到程序中
2、需要ViewModel 和 View 做绑定
3、当引入 ViewModel 的时候,要检查内存管理和循环引用
VIPER
View(ViewController)
向用户展示UI,并且把UI上的操作转发给Presenter去处理。
Presenter
处理 View 发送的 UI 动作,这种处理完全独立于 UI 控件。除此之外,如果需要Presenter 还可以借助 Interactor 来处理模型(实体)。
Interactor
主要包含了业务逻辑并且集成了程序的其他部分,它也拥有并且管理Entity。
Entity
只是愚蠢的使用数据结构存储数据,被Interactor持有,对其他模块是隐藏的。
Router
它明确处理来自 Presenter 的导航操作。Router 可以很好的通过 Presenter 注入依赖。导航是 Router 负责的唯一职责。