iOS 设计模式 - MVC, MVP, MVVM,VIPER

473 阅读9分钟

iOS 设计模式 - MVC, MVP, MVVM,VIPER

Original - MVC

Original-MVC-representation.png

Apple 可能考虑过不将 View 与 Model 紧密绑定,因为移动应用程序中的View 可以高度重用。

如果保持紧密耦合,我们每次都需要为不同的Model重新实现View。

Model 和 View相关独立 是 Apple MVC 背后的实际想法。

Apple-MVC

Apple-MVC-representation.png

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)

Model-View-Presenter.png

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 :

iOS Swift : MVP Architecture

github.com/farabi/MVPE…

MVVM (Model-View-View Model)

Model-View-View Model.png

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.youtube.com/watch?v=qzX…

medium.com/swlh/ios-de…

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

VIPER-module-separation .png

View(ViewController)

向用户展示UI,并且把UI上的操作转发给Presenter去处理。

Presenter

处理 View 发送的 UI 动作,这种处理完全独立于 UI 控件。除此之外,如果需要Presenter 还可以借助 Interactor 来处理模型(实体)。

Interactor

主要包含了业务逻辑并且集成了程序的其他部分,它也拥有并且管理Entity。

Entity

只是愚蠢的使用数据结构存储数据,被Interactor持有,对其他模块是隐藏的。

Router

它明确处理来自 Presenter 的导航操作。Router 可以很好的通过 Presenter 注入依赖。导航是 Router 负责的唯一职责。

References :

anupharbade.medium.com/ios-design-…