为什么要用MVVM替代MVC
在MVC模式中,Controller由于承担了过多的事务,包括页面展示逻辑和业务逻辑,往往会变的臃肿不堪,成为一个人们所说的重量级视图控制器。臃肿的ViewController难以理解,难以维护,难以扩展,增加了后续开发的复杂度,降低了整体开发的效率。
什么是MVVM
MVC
MVC(Model-View-Controller)是最经典的架构模式,其中Model是作为数据管理者,View作为数据展示者,Controller作为数据加工者,Model和View又都是由Controller来根据业务需求调配,所以Controller还负担了一个数据流调配的功能。
MVCS
MVCS是基于MVC衍生出来的一套架构。从概念上来说,它拆分的部分是Model部分,拆出来一个Store。这个Store专门负责数据存取。
MVVM
MVVM本质上也是从MVC中派生出来的,它是一个精心优化的MVC架构。它把数据加工的任务从Controller中解放了出来,使得Controller只需要专注于数据调配的工作,ViewModel则去负责数据加工并通过通知机制让View响应ViewModel的改变。大部分MVVM架构都会使用ReactiveCocoa,ReactiveCocoa带来了信号通知效果。
当然MVVM也有缺点,数据绑定机制让调试更困难,界面上出现的bug,可能是view的代码有问题,也可能是model有问题。数据绑定机制让一个位置的bug快速传递到其它位置上,定位原始出问题的地方不那么容易。而且数据绑定需要花费更大的内存。MVVM的学习和开发成本也很高,大家对它不熟悉,基于绑定机制进行编程需要一定时间的学习才能上手。
MVVM简化版
MVVM简化版是在MVC和MVVM两种架构中权衡而产生的架构。它引用了ViewModel,将表示逻辑移到ViewModel中。一个view对应一个ViewModel,其包含了这个View数据展示和样式定制所需要的所有数据。同时,不再引用双向数据绑定机制,而使用传统的代理回调将ui事件传递给外界。这个架构中,ViewModel负责处理数据展示和样式定制的逻辑; ViewController仅仅负责数据流调配。
MVVM简化版有以下三个优点:
- 简单,易学易用,上手成本低
- 兼容MVC模式
- 提高应用的可测试性
怎么使用MVVM
下面我们用MVVM简化版写一个简单的demo,看看它是如何工作的。
这是一个获取最新天气的demo,为简单起见,只显示今天的天气。从服务返回的json格式如下:
......
"today": {
"temperature": "28℃~38℃",
"date_y": "2016年08月19日",
"weather_id": {
"fa": "00",
"fb": "00"
},
"city": "杭州",
......
}
要求在页面上按MMDD格式显示日期,天气情况不能用00、01直接显示,要转换一下。这些都属于展示逻辑。我们将它从ViewController中移到ViewModel中。
首先建立一个Weather model,它仅仅定义了几个属性来表达数据,没有任何数据加工的部分。
struct Weather {
var location: String? //位置
var date: String? //日期
var iconText: String? //天气图标
var temperature: String? //温度
}
WeatherViewModel有自己的属性,提供给对应的View。它传入weather model,加工数据以满足view需要。
class WeatherViewModel: NSObject {
var date: String?
var iconText: String?
var loction: String?
var temperature: String?
......
展示逻辑的处理如下:
class func viewModelWith(weather: Weather) -> WeatherViewModel{
let date = weather.date?.toDate(DateFormat.Custom("yyyy年MM月dd日"))
var iconText = ""
switch weather.iconText! {
case "00":
iconText = "晴"
case "01":
iconText = "多云"
case "02":
iconText = "阴"
case "03":
iconText = "阵雨"
case "04":
iconText = "雷阵雨"
case "07":
iconText = "小雨"
case "08":
iconText = "中雨"
case "09":
iconText = "大雨"
case "10":
iconText = "暴雨"
case "11":
iconText = "特大暴雨"
case "21":
iconText = "小雨-中雨"
case "22":
iconText = "中雨-大雨"
case "23":
iconText = "大雨-暴雨"
default: break
}
let wvm = WeatherViewModel()
wvm.date = String(date!.month) + "\\" + String(date!.day)
wvm.loction = weather.location
wvm.temperature = weather.temperature
wvm.iconText = iconText
return wvm
}
View只需要定义好装配ViewModel的接口和定义好UI回调事件即可。
class WeatherView: UIView {
var timeLbl: UILabel!
var iconLbl: UILabel!
var temperationLbl: UILabel!
var cityLbl: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func bindDataWithViewModel(weatherViewModel: WeatherViewModel){
timeLbl.text = weatherViewModel.date
iconLbl.text = weatherViewModel.iconText
temperationLbl.text = weatherViewModel.temperature
cityLbl.text = weatherViewModel.loction
}
......
ViewController管理View Controller的生命周期,负责生成所有的View实例,并放入到View Controller中。监听处理来自View和业务相关的事件。
......
let weather = Weather(location: location, date: date, iconText: icon, temperature: temperature)
let wv = WeatherViewModel.viewModelWith(weather)
self.todayView.bindDataWithViewModel(wv)
......
demo的源码在这里:[https://github.com/superzcj]
总结
MVVM将展示和业务逻辑从Controller中移到ViewModel,为Controller减轻了负担,使代码结构更清晰,职责更明确,同时使代码更易于测试。
简化的MVVM也能跟现有的MVC应用兼容,不会对MVC模式带来太多变化,只是把部分逻辑代码移动个位置。另外对于简单的业务功能,使用MVVM会频繁在各个部分传递数据,反而显得臃肿罗嗦。因此,对于简单的业务,我们仍可以用MVC模式开发,对于复杂的业务,我们使用MVVM模式拆分业务逻辑。