适合的才是最好的
我理解的架构划分+组装,就是用最简单的办法在遵循单一责任原则的前提下把责任划分到多个实体,然后再把实体进行组装,达到便于维护和使用的目的。
MVC
Model-View-Controller。MVC简单地将一个模块分为3部分:
View是展示给外部的界面
Model是Controller内部管理的数据模型,和各种数据操作工具
Controller负责将Model的变化更新到View
Controller负责处理来自View的事件
MVC的划分粒度很粗,View依赖于VC的生命周期不易测试,只能测试 Model,实现功能代码量最少。
mport UIKit
struct Person { // Model
let name: String
}
class MineViewController : UIViewController { // View + Controller
var person: Person!
let showGreetingButton = UIButton()
let greetingLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
self.showGreetingButton.addTarget(self, action:#selector(didTapButton(button:)), for: .touchUpInside)
}
@objc func didTapButton(button: UIButton) {
let greeting = self.person.name
self.greetingLabel.text = greeting
}
// layout code goes here
}
// Assembling of MVC
let model = Person(name: "Niu")
let view = MineViewController()
view.person = model;
MVP
Model-View-Presenter用一个Presenter,把Controller中View的部分剔除,实现了View和Model的隔绝。
各部分分工如下:
View+VC负责界面展示和布局管理,向Presenter暴露视图更新和数据获取的接口
Presenter负责接收来自View的事件,通过View提供的接口更新视图,并管理Model
Model和MVC中的一样,提供数据模型和数据操作
在iOS里,UIView和UIViewController共同组合成了MVP中的View。UIView负责元素的展示,UIViewController负责界面布局和组合,并把事件转发给Presenter。
在MVP里,业务逻辑被放到了Presenter中,由它负责协调View和Model。
由于View的抽离,Presenter的状态是可控的,在测试时更不容易受外部影响。
作为中转站的 Presenter 与视图控制器的生命周期没有任何关联,并且 View 很容易被模拟,所以在 Presenter 中没有任何页面布局的代码
在iOS中使用MVP很简单,在View和Presenter之间你必须通过Protocol手工准备数据和做事件绑定。
缺点
多了一层用于隔离的接口,会导致代码数量增大。随着界面越来越复杂,Presenter中的业务代码也会越来越庞大,会遇到如何再细分Presenter的问题。
import UIKit
struct Person { // Model
let name: String
}
protocol MineProtocol {//给视图提供刷新的方法
func setName(name: String)//出口
}
protocol MinePresenterProtocol {//调用视图的刷新方法
init(view: MineProtocol, person: Person)
func showName() //入口
}
class MinePresenter : MinePresenterProtocol {
let view: MineProtocol
let person: Person
required init(view: MineProtocol, person: Person) {
self.view = view
self.person = person
}
func showName() {
let name = self.person.name
self.view.setName(name: name)
}
}
class MineViewController : UIViewController, MineProtocol {
var presenter: MinePresenterProtocol!
let nameButton = UIButton()
let nameLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
self.nameButton.addTarget(self, action: #selector(didTapButton(button:)), for: .touchUpInside)
}
@objc func didTapButton(button: UIButton) {
//从model获取名字,然后调用setName显示名字
self.presenter.showName()//入口
}
//出口
func setName(name: String) {
self.nameLabel.text = name;
}
// layout code goes here
}
// Assembling of MVP
let model = Person(name: "niu")
let view = MineViewController()
let presenter = MinePresenter(view: view, person: model)
view.presenter = presenter
MVVM
Model-View-ViewModel模式,它也和MVP一样,目的是解决View和Model的耦合。
各部分分工如下:
Model提供数据模型
View负责视图展示
ViewModel用于描述View的状态,例如View的颜色、显示的文字等属性类的信息,将View抽象成了一个特殊的模型,并且持有和管理Model,维护业务逻辑
在MVP中,View通过接口的方式来描述自己,在MVVM中,则通过ViewModel来描述自己的特征。那么ViewModel如何将自己的变化更新到View上呢?MVVM经常和数据绑定一起出现,在UIViewController中,将View和ViewModel的属性用类似KVO的方式进行绑定,这样ViewModel的变化就能立即传输到View上。
数据绑定
利用ReactiveCocoa和RxSwift这些函数式响应编程框架实现数据绑定,可以用很少的代码完成复杂的业务逻辑,熟练时能够提升开发速度。但是数据绑定的缺点也很明显:调试困难,数据来源难以回溯,在线上出bug的时候就很难追踪了,所以从这方面来说又降低了维护的效率。
其实数据绑定只是一种为了减少胶水代码的技术实现方式,MVVM的设计并没有要求必须要使用数据绑定,你也完全可以使用protocol的方式来将ViewModel的变化传递给View,让数据流向更清晰。MVVM的关键是将View进行了抽象,从而实现View和Model的解耦。
ViewModel的职责
但是除了数据绑定,MVVM还有另一个问题。把业务逻辑放到ViewModel中,虽然能够为UIViewController减负,但是只是把问题转移了,最终ViewModel还是会变成另一个Massive ViewModel。
而且当ViewModel维护Model和业务逻辑时,可复用性就会大大降低。例如把同一个登录界面复用到另一个app中时,login model中的属性名或者类型很可能会改变,从而数据处理的方式也会改变,导致ViewModel无法重用。而当View由多个子View组成时,ViewModel里也会引入多个子ViewModel,这就又导致了View的实现影响了ViewModel的实现。奇怪的是,国内iOS圈对这个问题的探讨十分稀少。
ViewModel到底是什么?从它的命名和最初的设计来看,它只是View的抽象,目的是方便和Model进行数据转换。而默认把业务逻辑也放到ViewModel里,大概是由于objc.io上那篇文章的影响。其实在微软的WPF和前端里,MVVM的业务逻辑大部分是放在Model层的。
而针对这个问题,有人又提出了一个MVVMP架构(Model-View-ViewModel-Presenter),把业务逻辑放到了Presenter里。Presenter的引入让ViewModel专注于View的抽象,和Model分离开来,只负责管理View相关的状态、传递View的事件,因此ViewModel中的代码可以得到很好的复用。而Presenter负责大部分业务逻辑,如果模块需要重用,则把业务逻辑中的数据操作逻辑(domain logic)单独分离出来作为重用代码,其他的无法重用的应用逻辑(application logic)则依旧放在Presenter里。
和MVP相比,MVVM用了一种更优雅的方式来抽象View。但它和MVP其实是类似的,只做了View和Model的解耦,仍然没有对Controller进行进一步的细分。
“绑定”来源于 OS X 开发,但是在 iOS 的工具中并没有这个。虽然我们有 KVO 和通知机制,但是都不如绑定来的方便。
那么如果我们不想自己写一套的话,我们有两种选择:
- 选一个基于 KVO 的绑定框架,如:RZDataBinding或者SwiftBond
- 使用全量级的函数式响应编程的框架,如ReactiveCocoa、RxSwift或者