美文网首页人猿星球iOS相关iOS之设计模式
iOS 关于MVC和MVVM设计模式的那些事

iOS 关于MVC和MVVM设计模式的那些事

作者: CoderMikeHe | 来源:发表于2017-06-08 02:09 被阅读5758次
一、概述
  • 在 iOS 开发中,MVC(Model View Controller)是构建iOS App的标准模式,是苹果推荐的一个用来组织代码的权威范式。Apple甚至是这么说的。在MVC下,所有的对象被归类为一个Model,一个View,和一个ControllerModel持有数据,View显示与用户交互的界面,而ViewController调解ModelView之间的交互。现在,MVC 依然是目前主流客户端编程框架,但同时它也被调侃成Massive View Controller(重量级视图控制器),想必开发者在开发中无可避免被下面几个问题所困扰:

    • 厚重的ViewController
    • 遗失的网络逻辑(无立足之地)
    • 较差的可测试性
  • 为了避免和解决上述问题的产生,从MVC引申出来一种维护性较强耦合性低的新的架构MVVM(Model View View-Mode)MVVM正式规范了视图和控制器紧耦合的性质,并引入新的组件。MVVM主要目的是为了分离视图(View)模型(Model)

  • 本文只是分享一下笔者对MVCMVVM的一些见解,在此抛砖引玉,希望能为存在对MVCMVVM迷茫的广大开发者提供一点思路,少走一些弯路,填补一些细坑。文章仅供大家参考,若有不妥之处,还望不吝赐教,欢迎批评指正。

二、MVC(Model View Controller)
  1. MVC之间的关系
    任何一个正经开发过软件的人都熟悉MVC,它意思是Model View Controller, 是一个在复杂应用设计中组织代码的公认模式,它们之间的结构关系如下:
    MVC示意图.png

我们看到的只是一个苹果 **典型的MVC ** 设置。view将用户交互通知给controllerview controller通过更新model来反应状态的改变。model(通常使用Key-Value-Observation)通知controller来更新他们负责的view。大多数iOS应用程序的代码使用这种方式来组织。然而,典型的MVC架构不适用于当下的iOS开发。尽管从技术上看ViewController 是相互独立的,但事实上它们几乎总是结对出现,一个 View 只能与一个 Controller 进行匹配,反之亦然。既然如此,那我们为何不正规化它们的连接:

MVC示意图2.png

因此,M-VC 可能是对 iOS 开发中的 MVC模式更为准确的解读,同时更也准确地描述了我们日常开发可能已经编写的 MVC 代码,但它并没有做太多事情来解决 iOS 应用中日益增长的重量级视图控制器的问题。(PS:躺枪了没...)

举例说明:


M-VC_Example.png

若假设笔者利用MVC的设计模式来开发此界面,那想必是这样的。

  • M:SUGoods(商品模型Model)
  • V:SUGoodsCell(展示商品数据的View,自定义的 UITableViewCell)
  • C:SUHomeViewController (首页控制器Controller)

控制器(SUHomeViewController)代码实现

- (void) requestRemoteData
{
   // 1.发起网络请求,获取到服务器的数据,并将其转化成模型数据(`SUGoods`)
   // 2.添加到数据源(`dataSource`)
   // 3.最后刷新表格`[self.tableView reloadData]`,配置`SUGoodsCel`l即可
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    SUGoodsCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Goods"];
    cell.goods = self.dataSource[indexPath.row];
    return cell;
}

View(SUGoodsCell)代码实现

SUGoodsCell.h
@class SUGoods;
@interface SUGoodsCell : UITableViewCell
@property (nonatomic, strong) SUGoods *goods;
@end

SUGoodsCell.m
@implementation SUGoodsCell
- (void)setGoods:(SUGoods *)goods
{
     _goods = goods
     /// config data ....
}
@end 

都写到这份上了,大家用脚趾头想想,这个SUGoodsCell,正是由View直接来调用Model,所以事实上典型的MVC的原则已经违背了,但是这种情况是一直发生的甚至于人们不觉得这里有哪些不对。如果严格遵守MVC的话,你会把对cell的设置放在Controller中,不向View传递一个Model对象,这样就会大大增加Controller的体积。所以说,这哪里是典型的MVC设计模式,这分明是M-VC设计模式呀。简而言之,在理想的世界里,MVC也许工作的很好。然而,我们生活在真实的世界,谢谢(PS:让梦想实现的最好的方式,就是醒来!!!)。

  1. MVC的弊端
    MVC的利弊大家想必是有目共睹的,Massive View Controller的说法也并非空穴来风的。让我们一起探讨MVC的弊端,剖析问题产生原因,打造一个轻量级的ViewController,明确MVC设计模式中各个角色的职责。
  • 厚重的View Controller
    M:模型model的对象通常非常的简单。根据Apple的文档,model应包括数据操作数据的业务逻辑。而在实践中,model层往往非常薄,不管怎样,model层的业务逻辑不应被拖入到controller
    V:视图view通常是UIKit控件(component,这里根据习惯译为控件)或者编码定义的UIKit控件的集合。View的如何构建(PS:IB或者手写界面)何必让Controller知晓,同时View不应该直接引用model(PS:现实中,你懂的!),并且仅仅通过IBAction事件引用controller。业务逻辑很明显不归入view,视图本身没有任何业务。
    C:控制器controllerController是app的“胶水代码”:协调模型和视图之间的所有交互。控制器负责管理他们所拥有的视图的视图层次结构,还要响应视图的loadingappearingdisappearing等等,同时往往也会充满我们不愿暴露的model的模型逻辑以及不愿暴露给视图的业务逻辑
    网络数据的请求及后续处理,本地数据库操作,以及一些带有工具性质辅助方法都加大了Massive View Controller的产生。

  • 遗失(无处安放)的网络逻辑
    苹果使用的MVC的定义是这么说的:所有的对象都可以被归类为一个model,一个view,或是一个controller
    你可能试着把它放在Model对象里,但是也会很棘手,因为网络调用应该使用异步,这样如果一个网络请求比持有它的model生命周期更长,事情将变的复杂。显然View里面做网络请求那就更格格不入了,因此只剩下Controller了。若这样,这又加剧了Massive View Controller的问题。若不这样,何处才是网络逻辑的家呢?

  • 较差的可测试性
    由于View Controller混合了视图处理逻辑和业务逻辑,分离这些成分的单元测试成了一个艰巨的任务。若一个Massive View Controller有上万行代码,要你编写个单元测试,我敢保证,你不是想写,你是想死,分分钟填表走人。

三、MVVM(Model View View-Mode)

一种可以很好地解决Massive View Controller问题的办法就是将 Controller 中的展示逻辑抽取出来,放置到一个专门的地方,而这个地方就是 viewModelMVVM衍生于MVC,是对 MVC 的一种演进,它促进了 UI 代码与业务逻辑的分离。它正式规范了视图和控制器紧耦合的性质,并引入新的组件。他们之间的结构关系如下:

MVVM示意图.png
  • MVVM 的基本概念

    • MVVM 中,viewview controller正式联系在一起,我们把它们视为一个组件
    • viewview controller 都不能直接引用model,而是引用视图模型(viewModel
    • viewModel 是一个放置用户输入验证逻辑,视图显示逻辑,发起网络请求和其他代码的地方
    • 使用MVVM会轻微的增加代码量,但总体上减少了代码的复杂性
  • MVVM 的注意事项

    • view 引用viewModel ,但反过来不行(即不要在viewModel中引入#import UIKit.h,任何视图本身的引用都不应该放在viewModel中)(PS:基本要求,必须满足
    • viewModel 引用model,但反过来不行
  • MVVM 的使用建议

    • MVVM 可以兼容你当下使用的MVC架构。
    • MVVM 增加你的应用的可测试性。
    • MVVM 配合一个绑定机制效果最好(PS:ReactiveCocoa你值得拥有)。
    • viewController 尽量不涉及业务逻辑,让 viewModel 去做这些事情。
    • viewController 只是一个中间人,接收 view 的事件、调用 viewModel 的方法、响应 viewModel 的变化。
    • viewModel 绝对不能包含视图 view(UIKit.h),不然就跟 view 产生了耦合,不方便复用和测试。
    • viewModel之间可以有依赖。
    • viewModel避免过于臃肿,否则重蹈Controller的覆辙,变得难以维护。
  • MVVM 的优势

    • 低耦合:View 可以独立于Model变化和修改,一个 viewModel 可以绑定到不同的 View
    • 可重用性:可以把一些视图逻辑放在一个 viewModel里面,让很多 view 重用这段视图逻辑
    • 独立开发:开发人员可以专注于业务逻辑和数据的开发 viewModel,设计人员可以专注于页面设计
    • 可测试:通常界面是比较难于测试的,而 MVVM 模式可以针对 viewModel来进行测试
  • MVVM 的弊端

    • 数据绑定使得Bug 很难被调试。你看到界面异常了,有可能是你 View 的代码有 Bug,也可能是 Model 的代码有问题。数据绑定使得一个位置的 Bug 被快速传递到别的位置,要定位原始出问题的地方就变得不那么容易了。
    • 对于过大的项目,数据绑定和数据转化需要花费更多的内存(成本)。主要成本在于:
      • 数组内容的转化成本较高:数组里面每项都要转化成Item对象,如果Item对象中还有类似数组,就很头疼。
      • 转化之后的数据在大部分情况是不能直接被展示的,为了能够被展示,还需要第二次转化。
      • 只有在API返回的数据高度标准化时,这些对象原型(Item)的可复用程度才高,否则容易出现类型爆炸,提高维护成本。
      • 调试时通过对象原型查看数据内容不如直接通过NSDictionary/NSArray直观。
      • 同一API的数据被不同View展示时,难以控制数据转化的代码,它们有可能会散落在任何需要的地方。
四、总结
  • MVC的设计模式也并非是病入膏肓,无药可救的架构,最起码目前MVC设计模式仍旧是iOS开发的主流框架,存在即合理。针对文章所述的弊端,我们依旧有许多可行的方法去避免和解决,从而打造一个轻量级的ViewController
  • MVVMMVC的升级版,完全兼容当前的MVC架构,MVVM虽然促进了UI 代码与业务逻辑的分离,一定程度上减轻了ViewController的臃肿度,但是ViewViewModel之间的数据绑定使得 MVVM变得复杂和难用了,如果我们不能更好的驾驭两者之间的数据绑定,同样会造成Controller 代码过于复杂,代码逻辑不易维护的问题。
  • 一个轻量级的ViewController是基于MVCMVVM模式进行代码职责的分离而打造的。MVCMVVM有优点也有缺点,但缺点在他们所带来的好处面前时不值一提的。他们的低耦合性,封装性,可测试性,可维护性和多人协作便利大大提高了开法效率。
  • 同时,我们需要保持的是一个拥抱变化的心,以及理性分析的态度。在新技术的面前,不盲从,也不守旧,一切的决策都应该建立在认真分析的基础上,这样才能应对技术的变化。
五、期待
  1. 文章若对您有点帮助,请给个喜欢❤️,毕竟码字不易;若对您没啥帮助,请给点建议💗,切记学无止境。
  2. 针对文章所述内容,阅读期间任何疑问;请在文章底部批评指正,我会火速解决和修正问题。
  3. GitHub地址:https://github.com/CoderMikeHe
六、实战篇
七、参考链接

相关文章

  • MVVM

    1.iOS 关于MVC和MVVM设计模式的那些事

  • 基础知识梳理

    iOS基础 1.mvc、mvvm iOS 关于MVC和MVVM设计模式的那些事 2.autoReless 自动释放...

  • iOS 关于MVVM With ReactiveCocoa设计模

    一、概述 笔者 强烈推荐 大家在阅读本文之前,还请先移步阅读? iOS 关于MVC和MVVM设计模式的那些事 和...

  • iOS MVVM架构总结

    参考:iOS 中MVC设计模式iOS MVVM架构iOS MVVM-框架介绍iOS 架构模式MVVM的实践总结iO...

  • iOS 关于MVC和MVVM设计模式的那些事

    一、概述 在 iOS 开发中,MVC(Model View Controller)是构建iOS App的标准模式,...

  • iOS开发之MVVM+RAC架构模式

    在说MVVM之前,首先要了解我们最常用的MVC设计模式⬇️ 1.MVC设计模式 苹果官方将MVC设计模式作为iOS...

  • iOS 设计模式

    MVC和MVVM?它是iOS开发中阻力最低的架构模式。MVC代码量最小,设计开销最小的模式。MVC常见的问题: 在...

  • iOS-MVC,MVP,MVVM及VIPER简介

    iOS中MVC,MVP,MVVM及VIPER设计模式介绍的文章有很多,开发过程MVC最常见的模式,MVVM也经常被...

  • 设计模式概览

    iOS MVC中的设计模式 MVC是一种用户界面架构模式,同样的MVVM、MVP等都是MVC的变种,iOS平台中用...

  • iOS进阶基础

    iOS常用的设计模式有哪些?MVC和MVVM的区别? import和@include和区别?@class有什么作用...

网友评论

  • yeshenlong520:群主现在开发是纯mvvm还是mvc混用,我感觉首页一般cell类型多,模型也不一样,viewmodel写起来就没那么容易了,mvc倒是直观
    CoderMikeHe:@yeshenlong520 混用较多
  • 请叫我攻城狮:你这个是将c中的逻辑全都放到了vm中吧?
    CoderMikeHe:@请叫我攻城狮 VM和View的双向绑定是一个比较核心的地方, 我主页里面写了几篇文章来实践MVVM,你有时间看看,有问题或建议,都可以跟我说说。
    请叫我攻城狮:@CoderMikeHe 我看的最近都是vm和view双向绑定的,和你这个还有点差距
    CoderMikeHe:@请叫我攻城狮 嗯嗯 把C中的数据处理逻辑,放到VM中去了,但是UI逻辑还是得保留在控制器中
  • 夜雨聲煩_:楼主你好
    我把项目分为网络层,业务层和UI层三大层,
    在网络层里进行网络数据处理,在业务层进行业务逻辑处理、调用网络接口、开放接口给UI层。然后在UI层中采用MVC,C只是简单的调用业务接口得到的数据,按需转化成UI层中的Model,简单处理V和M的一些数据传递,C很轻量。
    楼主觉得这种思路怎么样?有什么问题么?
    MVVM加RAC造成超长的代码段,感觉可阅读性好差啊。。。
    CoderMikeHe:@夜雨聲煩_ 动画确实不好封装,但XIB能省一些布局代码。
    CoderMikeHe:@夜雨聲煩_ 你完全可以把 ViewModel的所处理的业务 理解成和你一样的的 【业务层】处理的业务。当然MVVM只是一种说法罢了,其实核心目的都是,把一些业务逻辑放到一个专门的业务层,而不是放到控制器即可。
    其次【MVVM加RAC造成超长的代码段,感觉可阅读性好差啊。。。】这个问题确实存在,但是个人觉得RAC还是比较优越的。看习惯了,也还好。
    夜雨聲煩_:而且我V习惯用代码编写,这样涉及到动画还有一些XIB无法实现的效果的时候都在V里,不会写在C里。感觉C臃肿的很大原因是使用XIB了吧
  • 9bebf4d7642b:下载你的demo pod install 总出现问题 项目有点大 建议把pod 对应的环境说下
    CoderMikeHe:@像一颗树生活着 可以用ReactiveObjC的,乱码的问题,我后续会解决。是之前用公司的电脑代码加密引起的。或者你把 Pods 文件夹的 ReactiveCocoa文件夹删除,然后pod install也可以的。
    9bebf4d7642b:@CoderMikeHe 就是下载你的百度网盘的demo出现的 我用ReactiveObjC这个吧 也能跑 感谢大佬提供学习demo
    CoderMikeHe:@像一颗树生活着 好的,你先下载笔者提供的百度云地址。
  • c737a410172c:樓主你好,
    我看了兩遍文章,跟持續研究你的demo專案,發現在MVC&MVVM資料夾裡面的架構中。
    是沒有Model & View的資料層。只有viewController跟ViewModel。

    想請問想請問View跟 Model都被放到哪裡去了? 看得不是很明白,感謝
    CoderMikeHe:@GIGO_893d 你的这些疑问,笔者在这两篇文章:https://www.jianshu.com/p/db8400e1d40e 以及 https://www.jianshu.com/p/a0c22492a620 已经提出了。建议你先看看
    c737a410172c:@CoderMikeHe 樓主 感謝你的解說,我剛剛花了一些時間去吸收,但我有比較更想了解的地方是,Model跟viewModel的職權區分。這篇文章比較沒有提到。
    我舉個假設
    以下是Model:
    #import "Person.h"

    @Implementation Person

    - (instancetype)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName {
    self = [super init];
    if (!self) return nil;

    _firstName = [firstName copy];
    _lastName = [lastName copy];

    return self;
    }

    @EnD

    以下是viewModel:
    #import "PersonListViewModel.h"
    #import "Person.h"
    #import <UIKit/UITableView.h>

    @interface PersonListViewModel ()

    @property (nonatomic, strong, readonly) PersonStore *store;
    @property (nonatomic, strong) NSArray *people;

    @EnD

    @Implementation PersonListViewModel

    #pragma mark - Lifecycle

    - (instancetype)initWithStore:(PersonStore *)store {
    self = [super init];
    if (!self) return nil;

    _store = store;

    RAC(self, people) = [[store fetchPeople] startWith:@[]];

    _hasUpdatedContent = [RACObserve(self, people) mapReplace:@(YES)];

    return self;
    }

    #pragma mark - Data Source

    - (NSString *)title {
    return @"People";
    }

    - (NSUInteger)numberOfPeopleInSection:(NSInteger)section {
    return self.people.count;
    }

    - (NSString *)fullNameAtIndexPath:(NSIndexPath *)indexPath {
    Person *person = [self personAtIndexPath:indexPath];
    return [NSString stringWithFormat:@"%@ %@", person.firstName, person.lastName];
    }

    - (Person *)personAtIndexPath:(NSIndexPath *)indexPath {
    return self.people[indexPath.row];
    }

    @EnD

    這兩個code看下來就是model是定義這個Person的資料結構,然後viewModel是在定義他若顯示在viewController+views需要怎樣的資料形式data formate。而RAC是扮演viewController 與viewModel 通知資料更新的角色。view 跟 viewCotroller ios本身已經預設好資料更新的通知方式。 這樣理解對嗎?

    那我有接下來的疑問,我一切的資料都不是存在手機裡,而是都是跟API 網路層要資料。那Model是負責去做request api的動作嗎? 還是說 要分另外一個API Networking的資料夾 去實作網路請求?Model只是 api class去request資料回來後,要放入的資料框架而已。
    我不怕清楚,資料去向第三方請求這個動作要放在model裡面 還是 viewModel?

    再麻煩你開導了!
    CoderMikeHe:@GIGO_893d 这里我是放在Base里面的,这里因为笔者要提供数据给 MVC 、 MVVM Without RAC 、 MVVM With RAC的数据,所以笔者就放在一个Base里面了,让你误解了, 实际开发中你只需要把Base中的Model , View拖过来就行。
  • HelloWorld_1986:写的很好
    CoderMikeHe:@HelloWorld_1986 对你有用即可,谢谢
  • 22d39a467bca:看了一下贵公司的几个APP,路还远啊,加油
    CoderMikeHe:@焦糖玛奇朵_e7cc 多谢多谢
    焦糖玛奇朵_e7cc:@CoderMikeHe 加油加油
    CoderMikeHe:@fuyun0751 公司都拖欠工资三个月了,目前正在找工作。:joy::joy::joy::joy:
  • 十月的回眸:分析的很到位
    CoderMikeHe:@喻天的Book 见笑拉,还有许多东西可以挖掘。
  • 95b028d71e59:文章写的很不错,博主也是个热心肠:+1: :+1:
    CoderMikeHe:@CTGZ2016 见字如面。
  • 88632a9e82ab:文章中说view 和 view controller 都不能直接引用model,而是引用视图模型(viewModel),我想问一下,cell里面如果不引用model,你怎么给控件赋值来显示数据?
    CoderMikeHe:@黑猫了个警长 这很好理解呀,假设你的userModel字段有50个字段,但是你cell显示只需要其三个字段(avatar, nickname , gender),且这三个字段都需要处理,比如avatar后台返回的预览图片的地址,需要我们前端替换其中某个字符串即可获得高清图片的地址,nickname需要转换成富文本的样式,gender后台返回的数据是0,1代表男、女,需要我们前端处理显示男女等等。如果直接传个userModel,首先,cell只需要三个字段,而userModel却有50个字段,单一职责就不能够提现,其次上面三个字段都需要一定的计算逻辑才能用于cell显示,虽然在MVC大多数情况下,这部分逻辑都是在UserModel中处理好,暴露一个readonly属性出来,但是你这样会导致userModel的字段会继续增多,随着不断的迭代,这个userModel就变得不像之前后台返回的数据了,这样就形成了胖模型了;当然,你也可以把这个逻辑放在cell里面处理,但是我们一般知道要避免在cell里面计算,所以这个时候,你用一个UserViewModel来处理以上的三个小逻辑,那么更能体现单一职责化,到时候你只需要负责显示viewModel提供的东西即可,而刚刚那些恶心的代码就可以规避在userViewModel中去。这只是一个较为简单的例子,cell显示比较复杂且逻辑代码恶心等情况,你不觉得这样会更加简单呢。建议你先实践一下MVVM。
    88632a9e82ab:@CoderMikeHe 我知道“cell只负责显示viewModel提供的东西即可。” 举例说明我显示10条userModel数据在tableView的cell上,丢给cell的如果不是userModel那是什么呢
    CoderMikeHe:@黑猫了个警长 cell要显示的内容,直接有VIewModel来提供,把一些需要计算的,需要拼接字段的逻辑...都交给ViewModel来处理。cell只负责显示viewModel提供的东西即可。
  • 郑明明:很有个人特点的文章,不过有些地方过于参考其他文章了,导致别的地方没解释清楚,这里也只是一笔带过,可以注意下这个问题
    CoderMikeHe:@NtZheng 嗯嗯,确实有点草率了,后期在这里继续补充补充!
  • 949722fe4ac2:弱弱的问一句,是不是认识刀哥?
    CoderMikeHe:@fighting_BJ 哈哈,感谢感谢。我仍需努力。
    949722fe4ac2:@CoderMikeHe 恩,感觉你们的风格有点像,都是那种让人看起来很舒服,很严谨的风格,看你的文章很受益,谢谢!
    CoderMikeHe:@fighting_BJ 不认识,但知道他。早年间看过他讲iOS视频。
  • csqingyang:网络请求作为对实体数据的请求和处理, 不应该暴露在 ViewModel 层, 放在对应的 Model 层其实比较合理.
    CoderMikeHe:@csqingyang 我目前网络请求 参考的做法是https://github.com/octokit/octokit.objc
  • 简单的沙砾:讲的非常清晰明了,非常感谢楼主:+1:
    CoderMikeHe:嗯嗯。希望对你有些许帮助。
  • 我在敲BUG:很厉害:+1:,请问有demo么,加深下理解
    CoderMikeHe:@明月水上 嗯嗯 ,感谢。Demo正在写,但是请先看看 RAC的使用。
  • 空白少侠:写的非常好 我觉得题主应该讲MVC中涉及到的举例, 用MVVM的方式并且用代码写出来,这样会更加清晰。
    CoderMikeHe:@wuliwuli龙哥 嗯嗯,多谢鼓励。我正在写。后期会完成,敬请期待。
  • 我吃蕃茄与西红柿:很好。很好 新人 iOS
    CoderMikeHe:谢谢。希望能对你有用。

本文标题:iOS 关于MVC和MVVM设计模式的那些事

本文链接:https://www.haomeiwen.com/subject/hcggfxtx.html