浅析iOS开发的那些架构:MVC/MVP/MVVM

藏宝库编辑 2024-9-26 02:21:59 31 0 来自 中国
前言

很早以前就想总结一下,iOS开发中常用的一些架构:MVC、MVP、MVVM;但是不绝感觉本身没有明确透彻,由于发现本身明确的和网上其他人的总是有收支;网上的众口纷纭,仁者见仁智者见智;
随着经验的增长,本身对于这些架构的明确每次都有差异的收获,渐渐的大概和最初相识的情况大相径庭;
如今转念一想,架构这些事变并没有绝对的对错,也不会有什么尺度答案;每个人都会结合本身的经验加以明确,实践出最符合本身项目标架构;只要明确这些架构的底层逻辑、运用其办理项目中的标题,那就不消在乎具体的招式是什么了;
下面就谈谈我对MVC/MVP/MVVM的明确
MVC

MVC (Model-View-Controller) 是苹果保举的架构模式,也是其默认使用的架构模式。Apple官方MVC架构界说如下:
图示中简朴的列出了各层间的关系;结合iOS开发中实际场景,引用斯坦福的CS193p Paul老师的经典MVC图,更加清晰的阐明各层间的通讯:
2.png 各层职责


  • Model
    业务模子层
    Model封装了应用步伐的数据,也负责数据的获取及数据的处理处罚
    用户在View中所举行的创建或修改数据的操纵,通过Controller转达出去直接更新Model;Model数据更改时,它通过KVO或NotificationCenter等方式关照Controller,Controller再更新相应的View。
  • View
    视图层
    应用步伐中用户可以瞥见的对象都属于View层,对于iOS来说全部以UI开头的类根本都是这一层;View层负责界面元素表达(包罗动画效果)及相应用户操纵;
    Controller收到Model的更新关照后,通过引用关系直接更新View;View层所必要的表现数据,Controller可以通过dataSource提供;View相应变乱后通过delegate或Target-Action等方式反馈给Controller处理处罚;
  • Controller
    控制器层
    它相称于Model和View的中央人,负责Model和View的相互调配:当Model数据更改时更新对应的视图,当View更新或操纵后更新对应的数据;
别的Model层和View层是没有任何直接的关系的,它们之间的通讯都由Controller完成;
MVC架构的总的作用也体现出来了:

  • 镌汰耦合性:各层分工明确,低落了相互的关联,方便维护
  • 进步了代码重用性:Model层和View层解耦了,方便重用
  • 便于测试:在精确使用Model层的情况下,业务处理处罚和View、Controller完全解耦,可以单独测试业务逻辑;
MVC的困惑

但是在iOS实际开发中,逐步的我们会发现大部门人写的MVC已经偏离了抱负中的架构筹划;
一个显着的特性就是ViewController层变得特殊痴肥,代码异常的多,形成了另一种MVC:Massive ViewController;
原因有很多,紧张在于2点:

  • Controller层包罗View的表现逻辑
  • Model层误解、误用
下面通过一个demo来分析:MVC实现一个简朴的新闻列表界面;
如许的实当代码,只要入门级就能实现;
// model层  由服务器返回的数据:标题、时间、封面struct News {    let title: String    let createTime: String    let coverSrc: String}// view层,表现新闻数据class NewsTableViewCell: UITableViewCell {    let titleLabel = UILabel()    let dateLabel = UILabel()    let coverImageView = UIImageView()        override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {        super.init(style: style, reuseIdentifier: reuseIdentifier)        backgroundColor = .white                titleLabel.textColor = .black        dateLabel.textColor = .gray        coverImageView.contentMode = .scaleAspectFill        addSubview(titleLabel)        addSubview(dateLabel)        addSubview(coverImageView)                ...省略布局代码    }            required init?(coder: NSCoder) {        fatalError("init(coder has not been implemented")    }}// viewController,核心代码class NewsListViewController: UIViewController {    var newsList = Array<News>()    override func viewDidLoad() {        super.viewDidLoad()        view.backgroundColor = .white        view.addSubview(tableView)        view.addSubview(activityIndicator)                activityIndicator.startAnimating()        AF.request("https://c.3g.163.com/nc/article/list/T1467284926140/0-20.html").responseJSON { rsp in            self.activityIndicator.stopAnimating()            switch rsp.result {            case .success(let json):                print(json)                let jsonData = JSON.init(json)                let newsJsonArr = jsonData["T1467284926140"].arrayValue                self.newsList = newsJsonArr.map {                    let title = $0["title"].stringValue                    let createTime = $0["ptime"].stringValue                    let coverSrc = $0["imgsrc"].stringValue                    return News(title: title, createTime: createTime, coverSrc: coverSrc)                }                self.tableView.reloadData()            case .failure(let err):                print(err)            }        }    }    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {        var cell: NewsTableViewCell        if let rs = tableView.dequeueReusableCell(withIdentifier: cellId) as? NewsTableViewCell {            cell = rs        } else {            cell = NewsTableViewCell(style: .subtitle, reuseIdentifier: cellId)        }                    let news = newsList[indexPath.row]        cell.titleLabel.text = news.title        let dateFormatter = DateFormatter()        dateFormatter.dateFormat = "yyyy-MM-dd HH:mm"        let date = dateFormatter.date(from: news.createTime)        cell.dateLabel.text = dateFormatter.string(from: date ?? Date.now)        let url = URL(string: news.coverSrc.replacingOccurrences(of: "http:", with: "https:"))        cell.coverImageView.kf.setImage(with: url)                return cell    }}针对以上代码,分析存在的标题;
Controller包罗View的表现逻辑

根据定名就可以知道,ViewController并不是单独的Controller而已,Apple的Cocoa框架把View和Cotroller组合在一起,ViewController同时做了View和Controller的事变;这是它和典范的MVC的差异之处,严格意义上来说也算违背了MVC架构的原则了;
实际上Cocoa中的MVC架构如下:
4.png Cocoa为何要这么筹划?
在服务端开发范畴,Controller做完本身的事变之后,就把全部关于View的工作交给了页面渲染引擎去做,Controller不会去做任何关于View的事变,包罗天生View。这些都由渲染引擎代庖了。这是一个区别,但实在服务端View的概念和Native应用View的概念,真正的区别在于:从概念上严格分别的话,服务端实在根本没有View,拜HTTP协议所赐,我们寻常所讨论的View只是用于形貌View的字符串(更实质的应该称之为数据),真正的View是欣赏器。
以是服务端只管天生对View的形貌,至于对View的长相,UI变乱监听和处理处罚,都是欣赏器负责天生和维护的。但是在Native这边来看,本来属于欣赏器的任务也逃不掉要本身做。那么这件事变由谁来做最符合?苹果给出的答案是:UIViewController。
鉴于苹果在这一层做了很多费力卓绝的积极,让iOS工程师们不必切身去实现这些内容。而且,它把全部的功能都放在了UIView上,而且把UIView做成不但可以展示UI,还可以作为容器的一个对象。
看到这儿你明确了吗?UIView的另一个身份实在是容器!UIViewController中自带的谁人view,它的紧张任务就是作为一个容器
详见 Casa Taloyum文章 iOS应用架构谈 view层的构造和调用方案
在iOS开发中,View上面的变乱都是回传给ViewController,然后ViewController再另行调治,ViewController可以根据差异变乱的产生去很方便地更改容器内容;如demo中,tableView的刷新,加载提示activityIndicator的表现与否;
还有常用的,切换至无网络、无数据页面等等;
实际上iOS的MVC中Controller的职责会多几个:

  • 负责View的天生
  • 负责管理View的生命周期
因此,Cocoa针对MVC如许的处理处罚好坏常符合的;但是对于复杂界面,ViewController轻易出当代码膨胀,管理过多的状态,相应代码和逻辑代码肴杂一起,这将导致代码很难维护,很难测试;
关于这种原因导致的ViewController痴肥,业界也有一套办理方案;
ViewController有一个self.view的视图容器,那么我们也可以把ViewController看成一个管理各个View的容器;简朴来说就是:将一个原业务ViewController拆分成容器coordinate vc和对应的业务child vc来调和工作;之前的vc有多个页面的,这时就拆分成多个child vc;

  • child vc负责view的天生、相应view的变乱,管理本身view的生命周期;
  • coordinate vc负责创建child vc,将child vc的视图添加到本身的self.view容器上;同时管理view的生命周期、控制child vc获取数据等操纵;
这里不再单独写demo演示,各人只要类比UITabBarController就明确啥意思了。
这种方案实质上就是将原先痴肥的vc平摊到每个child vc了,简直也是一种优化方式,但是如果页面逻辑比力多的情况child vc也轻易出现一样的标题,那还得将child vc再拆分下去,而且vc多了也轻易出现调和的代码变多、复杂;是否使用这种方案,必要结合本身项目具体情况决定;
Model层误解、误用

如果说上面这个因素是由于Apple本身筹划引起,那 Model层误解、误用就完全是开发职员本身的原因了;
大部门人将Model明确成:只是单独的数据模子;
如上面demo中News对象,只有和服务器数据对应的几个字段的数据模子;如果只是数据模子,它也称不上是层;
另一个标题体现是:Controller里的var newsList = Array<News>(),我们即是把Model放到了Controller里,Model无法与Controlle 举行有用的通讯 (MVC图中的Notification & KVO 部门)
实际上Model层精确界说是业务模子层,也就是全部业务数据和业务逻辑都应该界说在Model层内里。
由于将Model层只是当成了数据模子,导致了业务数据、逻辑在MVC架构下无处安放;终极这些代码照旧堆砌到Controller层了;
5.png Model层的精确筹划:
M层要完成对业务逻辑实现的封装,一样寻常业务逻辑最多的是涉及到客户端和服务器之间的业务交互。M层内里要完成对使用的网络协议(HTTP, TCP,其他)、和服务器之间交互的数据格式(XML, JSON,其他)、本地缓存和数据库存储(COREDATA, SQLITE,其他)等全部业务细节的封装,而且这些东西都不能袒露给C层。全部供C层调用的都是M层内里一个个业务类所提供的成员方法来实现。也就是说C层是不必要知道也不应该知道和客户端和服务器通讯所使用的任何协议,以及数据报文格式,以及存储方面的内容。如许的长处是客户端和服务器之间的通讯协议,数据格式,以及本地存储的变动都不会影响任何的应用团体框架,由于提供给C层的接口稳定,只必要升级和更新M层的代码就可以了。好比说我们想将网络哀求库从ASI换成AFN就只要在M层变革就可以了,整个C层和V层的代码稳定。下面是M层内部条理的界说图:

详见:论MVVM伪框架布局和MVC中M的实现机制
针对Model进一步优化代码:
新增业务模子:
class NewsModel {    private(set) var itemList = Array<News>()        // MARK: -数据    var count: Int {        return itemList.count    }        func item(at index: Int) -> News {        return itemList[index]    }        /// 添加新的数据 (如上拉加载更多)    func append(newItems: [News]) {        itemList.append(contentsOf: newItems)    }        // MARK: -网络    func fetchAllDatas(callback: @escaping(_ success: Bool, _ errMsg: String) -> ()) {        // 这个哀求 视情况可以再单独封装一层网络层        AF.request("https://c.3g.163.com/nc/article/list/T1467284926140/0-20.html").responseJSON { rsp in            var result = true            var msg = ""            switch rsp.result {            case .success(let json):                print(json)                let jsonData = JSON.init(json)                let newsJsonArr = jsonData["T1467284926140"].arrayValue                self.itemList = newsJsonArr.map {                    let title = $0["title"].stringValue                    let createTime = $0["ptime"].stringValue                    let coverSrc = $0["imgsrc"].stringValue                    return News(title: title, createTime: createTime, coverSrc: coverSrc)                }            case .failure(let err):                print(err)                result = false                msg = err.localizedDescription            }                        callback(result, msg)        }    }        // 分页哀求    func fetchPartDatas(page: Int, callback: @escaping(_ success: Bool, _ errMsg: String) -> ()) -> Void {        // ....    }        // MARK: -本地存储    // .....        // MARK: -弱业务    func newsItemTitle(at index: Int) -> String {        return self.item(at: index).title    }        func newsItemDate(at index: Int) -> String {        let createTime = self.item(at: index).createTime        let dateFormatter = DateFormatter()        dateFormatter.dateFormat = "yyyy-MM-dd HH:mm"        let date = dateFormatter.date(from: createTime)        return dateFormatter.string(from: date ?? Date.now)    }        func newsItemCoverUrl(at index: Int) -> URL? {        let urlSrc = self.item(at: index).coverSrc        return URL(string: urlSrc.replacingOccurrences(of: "http:", with: "https:"))    }}ViewController相干业务代码迁移到Model层:
    override func viewDidLoad() {        super.viewDidLoad()        view.backgroundColor = .white        view.addSubview(tableView)        view.addSubview(activityIndicator)                activityIndicator.startAnimating()        newsModel.fetchAllDatas { success, errMsg in            self.activityIndicator.stopAnimating()            if success {                self.tableView.reloadData()            } else {                // 错误处理处罚            }        }    }    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {        var cell: NewsTableViewCell        if let rs = tableView.dequeueReusableCell(withIdentifier: cellId) as? NewsTableViewCell {            cell = rs        } else {            cell = NewsTableViewCell(style: .subtitle, reuseIdentifier: cellId)        }                let index = indexPath.row        cell.titleLabel.text = newsModel.newsItemTitle(at: index)        cell.dateLabel.text = newsModel.newsItemDate(at: index)        cell.coverImageView.kf.setImage(with: newsModel.newsItemCoverUrl(at: index))                return cell    }�优化后的代码,原先的数据News,照旧只保留服务器返回的数据字段;News跟业务完全无关,它的数据可以交给任何一个能处理处罚它数据的其他对象来完成业务。News是完全独立的,复用性很高也轻易维护;但如许相干的数据加工都丢到了业务模子NewsModel中(或相干helper中),News的操纵也会出如今各种地方;别的一种方式就是就是将相干的数据加工等弱业务交由数据对象本身处理处罚,但是后续该数据对象重用性将低落,更改的代码如下:
struct News {    let title: String    let createTime: String    let coverSrc: String        init(_ jsonData: JSON) {        title = jsonData["title"].stringValue        createTime = jsonData["ptime"].stringValue        coverSrc = jsonData["imgsrc"].stringValue    }        var newsItemDate: String {        let dateFormatter = DateFormatter()        dateFormatter.dateFormat = "yyyy-MM-dd HH:mm"        let date = dateFormatter.date(from: createTime)        return dateFormatter.string(from: date ?? Date.now)    }        var newsItemCoverUrl: URL? {        return URL(string: coverSrc.replacingOccurrences(of: "http:", with: "https:"))    }}它们各有优缺点,可以根据项目情况选择;
单向数据流

如果我们的demo中新闻列表支持2种加载方式,全量拉取、分页拉取;而且可以切换;对于分页加载会编写类似的代码:
   // 上拉加载更多    private func loadMore() {        activityIndicator.startAnimating()        newsModel.fetchPartDatas { success, errMsg in            self.activityIndicator.stopAnimating()            if success {                self.tableView.reloadData()            } else {                // 错误处理处罚            }        }    }可以发现,我们编写了同样的控制activityIndicator加载、tableView刷新的逻辑;也就是我们更改了数据后,仍必要手动的维护数据改动后带来的UI更新;如果后续还有更改数据的操纵(如删除一条、增长一条数据)等,还得继续类似的代码;如许一来重复代码过多,二来轻易堕落;
按照MVC架构的M和C的关系,Model数据更改后应该通过KVO或Notification的方式关照Controller更改View;从而实现 操纵-->更改数据、更改UI的流程 变革为 操纵 --> 更改数据 --> 更改UI的单向数据流;
Swift中用属性观测器代替KVO实现:
// Model层 监听数据变革并反馈给Controllerclass NewsModel {    private var itemList: Array<News> = [] {        didSet {            self.dataOnChanged?(())        }    }    private var isLoading: Bool = false {        didSet {            self.loadingChanged?(isLoading)        }    }        var dataOnChanged: ChangedBlock<Void>?    var dataOnError: ChangedBlock<Error>?    var loadingChanged: ChangedBlock<Bool>?....// 加载数据后不消再闭包回调    func fetchAllDatas() {        isLoading = true        AF.request("https://c.3g.163.com/nc/article/list/T1467284926140/0-20.html").responseJSON { rsp in            self.isLoading = false            switch rsp.result {            case .success(let json):                let jsonData = JSON.init(json)                let newsJsonArr = jsonData["T1467284926140"].arrayValue                self.itemList = newsJsonArr.map { return News($0) }            case .failure(let err):                self.dataOnError?(err)            }        }    }....Controller中绑定对应变乱,更新UI
// NewsListViewController        newsModel.dataOnChanged = { [weak self] _ in            self?.tableView.reloadData()        }                newsModel.dataOnError = { [weak self] err in            // 错误处理处罚        }                newsModel.loadingChanged = { [weak self] loading in            if loading {                self?.activityIndicator.startAnimating()            } else {                self?.activityIndicator.stopAnimating()            }        }然后,全部哀求数据、更改数据的代码就非常简朴了:加载框、tableview刷新都主动完成;
// 拉取全量数据     newsModel.fetchAllDatas()// 分页拉取数据      newsModel.fetchPartDatas()如果如今有新需求,再增长一个功能:
点击具体某条新闻时,更新新闻的阅读量并更新对应表现;
基于上面实现的单数据流,增长的代码将异常简朴;
数据Model、View增长对应的数据段和UI控件;具体逻辑全部可以使用业务模子完成;
// NewsModel 代码    // 增长更新阅读量的哀求(模仿)    func addReadCount(index: Int) {        isLoading = true        AF.request("https://www.baidu.com").response { rsp in            self.isLoading = false            switch rsp.result {            case .success:                var data = self.item(at: index)                data.readCount += 1                self.editData(at: index, newData: data)            case .failure(let err):                self.dataOnError?(err)            }        }    }    // 哀求乐成后,数据模子更改 (将触发属性观察器)    func editData(at index: Int, newData: News) {        itemList[index] = newData    }// NewsListViewController代码    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {        newsModel.addReadCount(index: indexPath.row)    }ps: 这里并不完善,由于只更改了一行数据,但是也是触发了dataOnChanged回调导致整个tableView全部刷新了;严谨的做法应该是,修改了哪行数据只刷新对应行的cell;具体做法可以将dataOnChanged回调细分,加一个返回参数体现是全部刷新、更新行刷新、新增行刷新、删除行刷新等;具体可以参考下方喵神的文章;
View是否依靠Model

MVC框架图中,View和Model是完全隔离的,它们间全部的交互都由Controller调和完成;
但实际开发中,当View的控件比力多,每个控件都必要设置的时间,Controller中相干赋值代码会特殊长;还有一点,当这个View在其他Controller重用时(绑定的数据模子一样的条件下),又必要重新写一样很长的一代码;为了开发方便同时镌汰Controller的代码量,大部门人会将Model直接丢给View,即View依靠于Model,然后内部设置控件数据;
// NewsTableViewCell    func configData(item: News) {        titleLabel.text = item.title        dateLabel.text = ...        coverImageView.kf.setImage ...    }// NewsListViewController    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {       ....       cell.configData(item)               return cell    }这种做法无可厚非,本身开发这件事就是机动变通;View依靠Model的长处分析了,但View的重用性,测试性都大大低落了;由于View依靠了具体的Model,其他模块必要重用View的或测试View的,必要额外设置出一个对应的具体Model;怎样决议?同样具体标题具体分析,如果View比力特殊只会在一个特定业务模块下使用,那绑定具体的Model益处更大;反之,就要思量View和Model隔离;实在还有一种更优的方式,就是View依靠于抽象而不是具体类;在iOS中,可以界说一个协议,协议界说必要提供给View数据的接口,必要绑定View的Model实现协议相干接口提供数据;
基于面向协议MVP模式下的软件筹划-(iOS篇) 这篇文章就偏重讲了面向协议编程,有爱好的可以看看;
随着不绝的优化,这个MVC架构实在就已经有了MVP、MVVM架构的雏形;MVP、MVVM本身也就是在MVC的根本上优化了,只不外它们形成了一套自有的规范。从本质上,照旧可以将他们称为MVC;
有了MVC的根本,接下来就简朴聊聊MVP、MVVM
MVP

MVP(Model-View-Presenter),是MVC架构的一个演化版本。是基于MVC误用的情况下的优化版;MVC误用上面已解说的很清晰了,MVP也是将业务逻辑和业务展示分离,它创建了一个视图的抽象也就是Presenter层,而视图就是P层的渲染效果。P层中包罗全部的视图渲染必要的数据如text、color、组件是否启用(enable),除此之外还会将一些方法袒露给视图用于某些变乱的相应。MVP并不是去掉了Controller,ViewController和View都归并归为View层,精确点说它应该叫MVCP;
如今常见的MVP架构模式实在都是它的变种:Passive View 和 Supervising Controller
这里这针对Passive View来分析;
Passive View(被动视图):View层是被动的,其任何状态的更新都交由Presenter处理处罚;View持有Presenter,View通过Presenter的署理回调来改变自身的数据和状态,View直接调用Presenter的接口来实行变乱相应对应的业务逻辑;这种方式包管了Presenter的完全独立,后续业务逻辑改动只必要更新Presenter而无需牵动View;但是带来另一个标题是,View耦合了Presenter,和MVC的View耦合Model一样可以使用协议方式优化;
各层职责


  • Model
    数据模子层
    单纯的数据字段,负责吸收数据、数据更改时关照Presenter。
  • View
    视图层(View and/or ViewController)
    ViewController也属于View层:负责View的天生,负责管理View的生命周期天生,实现View的署理和数据源;
    View: 监听Presenter层的数据更新关照, 刷新页面展示;将UI变乱反馈给Presenter;
  • Presenter
    业务逻辑层
    相称于Model和View的中央人,类似与MVC中ViewController的功能,也即将之前Controller的部门工作单独封装一层成为Presenter;负责实现View的变乱处理处罚逻辑,袒露相应的接口给View的变乱调用;收到Model数据更新后,更新对应View;
对比可以发现,Passive View方式的MVP和上面终极版的MVC并没有太大区别;无非是分层略有区别:将之前MVC的NewsModel更名NewsPresenter并归为Persenter层,实在就已经是最根本的MVP架构了;
9.png // NewsListViewController   let newsPresenter = NewsPresenter()  // 绑定Presenter....       newsPresenter.fetchAllDatas()       newsPresenter.fetchPartDatas()....   func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {       newsPresenter.addReadCount(index: indexPath.row)   }Presenter细分

上述代码中,我们将vc的self.view容器、tableView、loading当成一个view,绑定了唯一的Presenter (NewsPresenter);
实际上,当self.view由更多更复杂的view构成时,一个Presenter处理处罚的业务也会更多、更紊乱;实在可以为每一个独立的view设置单独的 Presenter;
下面我们将demo中的NewsTableViewCell都当做独立的view,每个cell设置一个NewsCellPresenter;
之前由NewsPresenter处理处罚的和cell有关的数据、业务全部挪到新的NewsCellPresenter,NewsPresenter的数据itemList生存为NewsCellPresenter; 其他tablView列表数据、加载框数据仍稳定;代码如下:
// 通过署理 presenter关照cell更新protocol NewsCellPresenterProtocol: AnyObject {    func updateReadCount(presenter: NewsCellPresenter)}class NewsCellPresenter {    weak var cell: NewsCellPresenterProtocol?        private(set) var newsItem = News()        // MARK: -弱业务    func newsItemTitle() -> String {        return self.newsItem.title    }    ....        // MARK: -网络 增长阅读数的逻辑    func addReadCount(callback: @escaping(_ success: Bool, _ errMsg: String) -> Void) {        AF.request("https://www.baidu.com").response { rsp in            var success = true            var msg = ""            switch rsp.result {            case .success:                self.newsItem.readCount += 1                self.cell?.updateReadCount(presenter: self)            case .failure(let err):                success = false                msg = err.localizedDescription            }                        callback(success, msg)        }    }}NewsTableViewCell和NewsCellPresenter通讯:
// NewsTableViewCell    var presenter: NewsCellPresenter = NewsCellPresenter() {        didSet {            presenter.cell = self            configData()        }    }    func configData() {        titleLabel.text = presenter.newsItemTitle()        ....    }    func updateReadCount(presenter: NewsCellPresenter) {        readCountLabel.text = presenter.newsItemReadText()    }// NewsListViewController    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {        ....        let cellPresenter = newsPresenter.cellPresenter(at: indexPath.row)        cell.presenter = cellPresenter                return cell    }        func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {        guard let cell = tableView.cellForRow(at: indexPath) as? NewsTableViewCell else { return }                // loading框界面由 外层newsView的newsPresenter负责 回调回来交由其处理处罚        newsPresenter.isLoading = true        cell.presenter.addReadCount { success, errMsg in            self.newsPresenter.isLoading = false        }    }MVVM

MVVM(Model — View — ViewModel),同MVP很相似,也是MVC架构的一个演化版本;
各层职责

从架构图可以看出来,MVVM和MVP险些完全一样;ViewModel层的作用实在就是和Presenter一样,其他层也和MVP一致;
在原有MVP demo根本上,改下类名根本上就是MVVM了:
MVVM实在是在MVP的根本上发展、改良的;改良的地方,就是图中和MVP中唯一差异的地方:
ViewModel和View之间参加了Binder层,可以实现双向绑定;
关于数据绑定,其着实MVC演进版的单数据流中已经实现过;为了区分MVP和MVVM,demo中MVP代码中的更新阅读数特意没有绑定;可以分析下他的弊端:


点击cell --> 通过presenter处理处罚逻辑 --> 处理处罚完毕更新数据,同时通过署理关照cell更新界面;
也就是每次都要把Present的状态同步到View,当变乱多起来的时间,如许写就很贫苦、且轻易堕落了;
这时就必要bind机制了,当状态、数据更改后主动更新对应的View;
照旧一样,通过属性观察器实现绑定:
class NewsCellViewModel {    // 通过属性观测器 绑定    var title = "" {        didSet {                    }    }    var createTime = "" {        didSet {                    }    }    var coverSrc = "" {        didSet {                    }    }    var readCount = 0 {        didSet {            self.readCountBind?(newsItemReadText())        }    }        // bind回调    var readCountBind: ValueBinder<String>?思量到每个数据值都能单独绑定,ViewModel中将item拆分,每个数据都监听值更改;值更改后主动回调给View更新;
然后ViewController中绑定:
        let cellViewModel = newsViewModel.cellViewModel(at: indexPath.row)        cell.viewModel = cellViewModel        // 绑定        cellViewModel.readCountBind = {[weak cell] countText in            cell?.readCountLabel.text = countText        }ViewModel中只要更改数据即可:
    func addReadCount(callback: @escaping(_ success: Bool, _ errMsg: String) -> Void) {        AF.request("https://www.baidu.com").response { rsp in...            case .success:                self.readCount += 1...        }    }MVVM迷惑


  • 是否必要Controller?
大部门人以为ViewModel做了Controller的事变,错以为MVVM不再必要Controller;
同MVP一样,固然它称为MVVM,但更精确来说应该是MVCVM;
13.gif Controller夹在View和ViewModel之间做的此中一个紧张事变就是将View和ViewModel举行绑定。在逻辑上,Controller知道应当展示哪个View,Controller也知道应当使用哪个ViewModel,然而View和ViewModel它们之间是相互不知道的,以是Controller就负责控制他们的绑定关系;

  • MVVM肯定必要RxSwift、ReactiveCocoa?
MVVM有数据绑定层,RxSwift、ReactiveCocoa等相应式框架很精致的实现了数据绑定;因此有部门人会以为MVVM必须共同RxSwift、ReactiveCocoa使用;
事实上MVVM的关键是ViewModel !!!
在ViewModel层,我们可以通过delegate、block、kvo、notification等实现数据绑定;
RxSwift、ReactiveCocoa等相应式框架做数据绑定,轻便优雅、有更加疏松的绑定关系可以或许低落ViewModel和View之间的耦合度;使用其可以更好体现MVVM的英华,但并不体现其是MVVM必不可少的;
如果项目中转向相应式编程,那MVVM+ RxSwift就是绝美共同;反过来,如果项目本身照旧用的体系的那一套编程方式,只是为了MVVM绑定而使用RxSwift等就是大材小用、得不偿失了
关于MVVM+ RxSwift的实现,这里就不做过多解析了,大概必要额外开一篇;
完备demo
(每个架构的类名一致,因此demo中想使用哪种架构就引用文件)
参考:
深入分析MVC、MVP、MVVM、VIPER
论MVVM伪框架布局和MVC中M的实现机制
iOS应用架构谈 view层的构造和调用方案
关于 MVC 的一个常见的误用
浅谈 MVC、MVP 和 MVVM 架构模式
不再对 MVVM 感到绝望
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-10-18 18:16, Processed in 0.163026 second(s), 35 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表