iOS MVVM 实践

为什么要用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模式拆分业务逻辑。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容