032*:MVP、MVVM+RAC、MVC

问题

MVVM和MVP的最大区别是采用了双向绑定机制,View的变动,自动反映在ViewModel上。

1:  MVP:   在数据模型M中发起请求,在Presenter组织好数据,通过协议,哪个view遵守了Presenter的协议,数据返回给指定的view;

2:  MVVM:

1:一个View对应一个ViewModel,View界面元素属性与ViewModel处理后的数据属性绑定。

2:而将对网络数据的处理的逻辑放在ViewModel中,也就是说,只有在有网络数据展示的View的ViewModel中,才会看见Model的影子,而处理过后的数据,将变成ViewModel的属性,

3:RAC:监听事件变换。
目录

1:MVC

2:MVVM+RAC

3:MVP 

预备

 

正文

1:MVC (Modal View Controller)(模型 视图 控制器)

一、MVC

从字面意思来理解,MVC 即 Modal View Controller(模型 视图 控制器),是 Xerox PARC 在 20 世纪 80 年代为编程语言 Smalltalk-80 发明的一种软件设计模式,至今已广泛应用于用户交互应用程序中。其用意在于将数据与视图分离开来。在 iOS 开发中 MVC 的机制被使用的淋漓尽致,充分理解 iOS 的 MVC 模式,有助于我们程序的组织合理性。

MVC 的几个明显的特征和体现:

View 上面显示什么东西,取决于 Model。

只要 Model 数据改了,View 的显示状态会跟着更改。

Control 负责初始化 Model,并将 Model 传递给 View 去解析展示。

1)Modal模型对象:

模型对象封装了应用程序的数据,并定义操控和处理该数据的逻辑和运算。例如,模型对象可能是表示商品数据 list。用户在视图层中所进行的创建或修改数据的操作,通过控制器对象传达出去,最终会创建或更新模型对象。模型对象更改时(例如通过网络连接接收到新数据),它通知控制器对象,控制器对象更新相应的视图对象。

2)View 视图对象:

视图对象是应用程序中用户可以看见的对象。视图对象知道如何将自己绘制出来,可能对用户的操作作出响应。视图对象的主要目的就是显示来自应用程序模型对象的数据,并使该数据可被编辑。尽管如此,在 MVC 应用程序中,视图对象通常与模型对象分离。

在iOS应用程序开发中,所有的控件、窗口等都继承自 UIView,对应 MVC 中的 V。UIView 及其子类主要负责 UI 的实现,而 UIView 所产生的事件都可以采用委托的方式,交给 UIViewController 实现。

3)Controller 控制器对象:

在应用程序的一个或多个视图对象和一个或多个模型对象之间,控制器对象充当媒介。控制器对象因此是同步管道程序,通过它,视图对象了解模型对象的更改,反之亦然。控制器对象还可以为应用程序执行设置和协调任务,并管理其他对象的生命周期。

控制器对象解释在视图对象中进行的用户操作,并将新的或更改过的数据传达给模型对象。模型对象更改时,一个控制器对象会将新的模型数据传达给视图对象,以便视图对象可以显示它。

对于不同的 UIView,有相应的 UIViewController,对应 MVC 中的 C。例如在 iOS 上常用的 UITableView,它所对应的 Controller 就是UITableViewController。

iOS MVC 示意图

MVC思维导图

1)Model 和 View 永远不能相互通信,只能通过 Controller 传递。

2)Controller 可以直接与 Model 对话(读写调用 Model),Model 通过 Notification 和 KVO 机制与 Controller 间接通信。

3)Controller 可以直接与 View 对话,通过 outlet,直接操作 View,outlet 直接对应到 View 中的控件,View 通过 action 向 Controller 报告事件的发生(如用户 Touch 我了)。Controller 是 View 的直接数据源(数据很可能是 Controller 从 Model 中取得并经过加工了)。Controller 是 View 的代理(delegate),以同步 View 与 Controller。

MVC自身不足

1)MVC 在现实应用中的不足:

在 MVC 模式中 view 将用户交互通知给控制器。view 的控制器通过更新 Model 来反应状态的改变。Model(通常使用 Key-Value-Observation)通知控制器来更新他们负责的 view。大多数 iOS 应用程序的代码使用这种方式来组织。

2)愈发笨重的 Controller

在传统的 app 中模型数据一般都很简单,不涉及到复杂的业务数据逻辑处理,客户端开发受限于它自身运行的的平台终端,这一点注定使移动端不像 PC 前端那样能够处理大量的复杂的业务场景。然而随着移动平台的各种深入,我们不得不考虑这个问题。传统的 Model 数据大多来源于网络数据,拿到网络数据后客户端要做的事情就是将数据直接按照顺序画在界面上。随着业务的越来越来的深入,我们依赖的 service 服务可能在大多时间无法第一时间满足客户端需要的数据需求,移动端愈发的要自行处理一部分逻辑计算操作。这个时间一惯的做法是在控制器中处理,最终导致了控制器成了垃圾箱,越来越不可维护。

控制器 Controller 是 app 的 “胶水代码”,协调模型和视图之间的所有交互。控制器负责管理他们所拥有的视图的视图层次结构,还要响应视图的 loading、appearing、disappearing 等等,同时往往也会充满我们不愿暴露的 Model 的模型逻辑以及不愿暴露给视图的业务逻辑。这引出了第一个关于 MVC 的问题...

视图 view 通常是 UIKit 控件(component,这里根据习惯译为控件)或者编码定义的 UIKit 控件的集合。进入 .xib 或者 Storyboard 会发现一个 app、Button、Label 都是由这些可视化的和可交互的控件组成。View 不应该直接引用 Model,并且仅仅通过 IBAction 事件引用 controller。业务逻辑很明显不归入 view,视图本身没有任何业务。

厚重的 View Controller 由于大量的代码被放进 viewcontroller,导致他们变的相当臃肿。在 iOS 中有的 view controller 里绵延成千上万行代码的事并不是前所未见的。这些超重 app 的突出情况包括:厚重的 View Controller 很难维护(由于其庞大的规模);包含几十个属性,使他们的状态难以管理;遵循许多协议(protocol),导致协议的响应代码和 controller 的逻辑代码混淆在一起。

厚重的 view controller 很难测试,不管是手动测试或是使用单元测试,因为有太多可能的状态。将代码分解成更小的多个模块通常是件好事。

3)太过于轻量级的 Model:

早期的 Model 层,其实就是如果数据有几个属性,就定义几个属性,ARC 普及以后我们在 Model 层的实现文件中基本上看不到代码(无需再手动管理释放变量,Model 既没有复杂的业务处理,也没有对象的构造,基本上 .m 文件中的代码普遍是空的);同时与控制器的代码越来厚重形成强烈的反差,这一度让人不禁对现有的开发设计构思有所怀疑。

4)遗失的网络逻辑:

苹果使用的 MVC 的定义是这么说的:所有的对象都可以被归类为一个 Model,一个 view,或是一个控制器。就这些,那么把网络代码放哪里?和一个 API 通信的代码应该放在哪儿?

你可能试着把它放在 Model 对象里,但是也会很棘手,因为网络调用应该使用异步,这样如果一个网络请求比持有它的 Model 生命周期更长,事情将变的复杂。显然也不应该把网络代码放在 view 里,因此只剩下控制器了。这同样是个坏主意,因为这加剧了厚重控制器的问题。那么应该放在那里呢?显然 MVC 的 3 大组件根本没有适合放这些代码的地方。

5)较差的可测试性:

MVC 的另一个大问题是,它不鼓励开发人员编写单元测试。由于控制器混合了视图处理逻辑和业务逻辑,分离这些成分的单元测试成了一个艰巨的任务。大多数人选择忽略这个任务,那就是不做任何测试。

二:MVVM

双向绑定
1.ViewModel驱动View :你请求数据之后,通过Block的回调到ViewController中,在ViewController中的view更新UI。
2.View驱动ViewModel 反向绑定也一样,View触发事件,更新对面ViewModel里面绑定的数据源,例如登录注册的Textfield,你输入和删除的时候,你的Model字段会对应更新,

2.1:当你提交的时候,通过set方法设置ViewModel的字段,就是已经更新的最新数据,这个时候ViewModel是最新数据

2.2:ViewModel中通过KVO监听ViewModel对应Key的变化,进而更新ViewModel的数据,做出响应。

比如你选择某个cell或者点赞的时候,View事件触发,拥有ViewModel,获取事件,更新绑定的ViewModel字段,更新数据源。用Block回调到View中,更新View。

用RACObserve来进行该字段开关的读取,如果监听到YES,就刷新对应的页面UI.

 

1:一个View对应一个ViewModel,View界面元素属性与ViewModel处理后的数据属性绑定。

Model只是在有网络数据的时候需要创建,它的作用只是一个数据的中专站,也就是一个极为简介的瘦model

这里弱化了Model的作用,而将对网络数据的处理的逻辑放在ViewModel中,也就是说,只有在有网络数据展示的View的ViewModel中,才会看见Model的影子,而处理过后的数据,将变成ViewModel的属性,注意一点,这些属性一定要尽量“直观”,比如能写成UIImage就不要写成URL

ViewModel和Model可以视情况看是否需要属性绑定

Controller的作用就是将主View通过与之对应的ViewModel初始化,然后添加到self.view,然后就是监听跳转逻辑触发等少部分业务逻辑,当然,ViewController的跳转还是需要在这里实现。 注意:这里面提到的绑定,其实就是对属性的监听,当属性变化时,监听者做一些逻辑处理,强大的框架来了————RAC

  • MVVM 的基本概念

    • MVVM中,view和 view controller正式联系在一起,我们把它们视为一个组件
    • view和 view 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展示时,难以控制数据转化的代码,它们有可能会散落在任何需要的地方。

扩展:ReactiveCocoa简介RAC

ReactiveCocoa是响应式编程(FRP)在iOS和OS中的一个实现框架,它的开源地址为:https://github.com/ReactiveCocoa/ReactiveCocoa

1:优点

RAC虽然最大的优点是提供了一个单一的、统一的方法去处理异步的行为,包括delegate方法、blocks回调、target-action机制、notificationsKVO

详细来说,在iOS开发过程中,当某些事件响应的时候,需要处理某些业务逻辑,这些事件都用不同的方式来处理。
比如按钮的点击使用action,ScrollView滚动使用delegate,属性值改变使用KVO等系统提供的方式。
其实这些事件,都可以通过RAC处理
ReactiveCocoa为事件提供了很多处理方法,而且利用RAC处理事件很方便,可以把要处理的事情,和监听的事情的代码放在一起,这样非常方便我们管理,就不需要跳到对应的方法里。非常符合我们开发中高聚合,低耦合的思想。

2:集成RAC

函数式编程(Functional Programming)和响应式编程(React Programming)也是当前很火的两个概念,它们的结合可以很方便地实现数据的绑定。 

于是,在 iOS 编程中,ReactiveCocoa 横空出世了,它的概念都非常 新,包括:

函数式编程(Functional Programming),函数也变成一等公民了,可以拥有和对象同样的功能,例如当成参数传递,当作返回值等。看看 Swift 语言带来的众多函数式编程的特性,就你知道这多 Cool 了。

响应式编程(React Programming),原来我们基于事件(Event)的处理方式都弱了,现在是基于输入(在 ReactiveCocoa 里叫 Signal)的处理方式。输入还可以通过函数式编程进行各种 Combine 或 Filter,尽显各种灵活的处理。

通过pods集成,集成步骤可以参考这两篇文章
ios 通过CocoaPods安装第三方库
Xcode如何集成Pod教程
ps:该三方库支持多个平台iOS、OS、Swift,iOS集成pod 'ReactiveObjC', '~> 3.1.0'

3:RAC常用语法

  • UITextField
    @weakify(self);
    [[self.testTextFileld rac_textSignal] subscribeNext:^(NSString * _Nullable x) {
    @strongify(self);
        NSLog(@"%@",x);
        self.testTextFileld.text = @"Hello";
    }];

监听了输入框内所有的变化,包括准备编辑,和退出编辑。再也不用写delegate了,编码起来方便快捷!!!

  • UIButton
    [[self.btn rac_signalForControlEvents:(UIControlEventTouchUpInside)] subscribeNext:^(__kindof UIControl * _Nullable x) {
        NSLog(@"%@",[x class]);
    }];

平常写按钮的触发事件都要新建一个方法去实现,现在不用了,直接在你的按钮下面写实现的代码。实例化和触发事件写在一起,查阅代码和维护代码更加直观!!!

  • NSNotificationCenter
    [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIApplicationDidEnterBackgroundNotification object:nil] subscribeNext:^(NSNotification * _Nullable x) {
        NSLog(@"%@",x);
    }];

还能监听通知的各种事件,上面就是监听了APP退到后台的事件。最重要的一点就是不需要移除通知,比通知用起来更爽,无后顾之忧!!!

MVVM+RAC代码

只需要把上述代码的init方法修改为RAC代码即可

- (instancetype)init{
    if (self == [super init]) {
        //订阅信号(热信号!!)    监听contenKey值的变化
        [RACObserve(self, contenKey) subscribeNext:^(id  _Nullable x) {
            NSArray *array = @[@"转账",@"信用卡",@"充值中心",@"蚂蚁借呗",@"电影票",@"滴滴出行",@"城市服务",@"蚂蚁森林"];
            NSMutableArray *mArray = [NSMutableArray arrayWithArray:array];
            [mArray removeObject:x];
            if (self.successBlock) {
                self.successBlock(mArray);
            }
        }];
    }
    return self;
}

上述代码已经实现了MVVM+RAC的开发

 

 

iOS 函数响应式编程RAC的学习 

三:MVP

  • 从字面意思来理解,MVP 即 Modal View Presenter(模型 视图 协调器),MVP 实现了 Cocoa 的 MVC 的愿景。MVP 的协调器 Presenter 并没有对 ViewController 的生命周期做任何改变,因此 View 可以很容易的被模拟出来。在 Presenter 中根本没有和布局有关的代码,但是它却负责更新 View 的数据和状态。MVC 和 MVP 的区别就是,在 MVP 中 M 和 V 没有直接通信。

  • MVP 是第一个如何协调整合三个实际上分离的层次的架构模式,既然我们不希望 View 涉及到 Model,那么在显示的 View Controller(其实就是 View)中处理这种协调的逻辑就是不正确的,因此我们需要在其他地方来做这些事情。例如,我们可以做基于整个 App 范围内的路由服务,由它来负责执行协调任务,以及 View 到 View 的展示。这个出现并且必须处理的问题不仅仅是在 MVP 模式中,同时也存在于以下集中方案中。

1)MVP模式下的三个特性的分析:

    • 任务均摊 -- 我们将最主要的任务划分到 Presenter 和 Model,而 View 的功能较少;
    • 可测试性 -- 非常好,由于一个功能简单的 View 层,所以测试大多数业务逻辑也变得简单;
    • 易用性 -- 代码量比 MVC 模式的大,但同时 MVP 的概念却非常清晰。

2)iOS MVP 示意图:

MVP1

    • 就 MVP 而言,UIViewController 的子类实际上就是 Views 并不是 Presenters。这点区别使得这种模式的可测试性得到了极大的提高,付出的代价是开发速度的一些降低,因为必须要做一些手动的数据和事件绑定。

    • 还有一些其他形态的 MVP -- 监控控制器的 MVP。这个变体包含了 View 和 Model 之间的直接绑定,但是 Presenter 仍然来管理来自 View 的动作事件,同时也能胜任对 View 的更新。

      MVP2

  • 3)规范的 MVP 设计模式:

    • 1、View 层比较简单明,就是 View 的一些封装、重用。在一款精心设计过的 App 里面,应该有很多 View 是可以封装重用的。比如一些自己的 TableViewCell,自己设计的 Button,一些 View(包含一些子 View,UI 精心设计过,在项目里多处出现的)等等。

    • 2、Model 层应该不仅仅是创建一个数据对象,还应该包含网络请求,以及数据 SQLite 的 CRUD 操作(比如 iOS 平台,一般以 FMDB 框架直接操作 sql,或者用 CoreData) 。一般可以将数据对象是否需要缓存设计成一个字段 isCache,或者针对整个项目设计一个开存储关,决定整个项目是否需要数据缓存。我们常见的新闻类 App,在离线的时候看到的数据,都是做了缓存处理的。比如一些金融类的 App,实时性比较高,是不做缓存的。

    • 3、Presenter 层并不涉及数据对象的网络请求和 SQLite 操作,只是 Model 层和 View 层的一个桥梁。Presenter 层就不至于太臃肿,容易看懂。一些大的 App,或因为上线时间比较久了,经历过众多程序员的修补,或因前期并未做好架构,以至于打开一个类,几千行的代码,看着自己都晕。

MVP全称Model-View-Presenter。顾名思义:

  • Model:与MVC中的model没有太大的区别。主要提供数据的存储功能,一般都是用来封装网络获取的json数据的集合。Presenter通过调用Model进行对象交互。
  • View:这里的View与MVC中的V又有一些小差别,这个View可以是viewcontroller、view等控件。Presenter通过向View传model数据进行交互。
  • Presenter:作为model和view的中间人,从model层获取数据之后传给view,使得View和model没有耦合。

总的来说MVP的好处就是解除view与model的耦合,使得view或model有更强的复用性。另外,MVP是面向协议的设计模式,下面看一张图:

理解:

1,controller,view是属于MVP中的V,model即为上图中的M,关键的Presenter,是对V,M的组织;
2,在数据模型M中发起请求,在Presenter组织好数据,通过协议,哪个view遵守了Presenter的协议,数据返回给指定的view;
3,V与M是完全解耦的,在controller界面进来,引入Presenter,通过协议,从而形成了请求数据--展示到指定的view上。其实是把逻辑处理放到了Presenter这里。

注意

 

引用

1:ios MVP 设计模式

2:iOS-MVVM设计模式

3:浅谈 MVC、MVP 和 MVVM 架构模式

posted on 2020-12-04 20:32  风zk  阅读(220)  评论(0编辑  收藏  举报

导航