论MVVM伪框架结构和MVC中M的贯彻机制

目录

直接都有人撰文吹捧MVVM应用开发框架,作品把MVVM说的悠扬并且批评包涵iOS和android所用的MVC经典框架。那篇小说就是想给这个捧臭脚的大千世界泼泼冷水,固然有可能导致骂声一片,然则目标是给那个刚入门的伙伴一些参考和提出,避防误入歧途。同时也给那些深陷其中不可能自拔的同伴们敲敲警钟,以免其在错误的征程上越走越远。

—— MVVM并非框架,而只是不难的公文夹分类 ——

MVVM被引入的前因后果

约莫是在二〇一〇年左右平移端支出火了四起,起先是iOS,Android,
WinPhone四个大平台竞争,后来后任退出了战斗,变成了二分天下。从使用系统布局以及为开发者提供的框架系列来看,三个阳台都是出产了经典MVC三层结构的开发格局,那三层所表示的意思是模型、视图、控制。那几个开发框架的初衷其实也很简单:视图负责浮现和渲染,模型负责作业逻辑的兑现,控制负责调度视图的事件以及工作逻辑的调用以及通告视图的刷新文告。
三有的松散耦合,各司其职。上边是经典的MVC框架结构:

[图形上传战败…(image-1d68cb-1512992093070)]

一个很可惜的真情是不管是Android和iOS都只对C和V两有的举行了规范的概念和落到实处:Android的视图部分的兑现是概念了各样控件以及因此XML文件来组装视图布局界面,iOS的视图的落实也是概念了各类控件以及经过XIB或SB来组装视图布局界面;
Android的主宰部分则是透过Activity来落到实处,而iOS的控制部分则是因而UIViewController来完毕的。而模型部分吗?因为各种应用的事务逻辑和运用场景并不同,所以多个平台也无能为力也不可能定义出一个通用的模型层出来,而是把模型层的概念留给了开发者来落实。但是那为大家的开发者在使用MVC框架开发应用时埋下了隐患。

前期的应用开发相对简单,因为尚未专业的模型层的概念,而控制层又在工程变更时留下了不少可供开发者写代码的地点,所以众多开发人员就放任自流的将事情逻辑、互连网请求、数据库操作、报文拼装和分析等等全部代码都放入了控制层里面去了,根本就不须要怎么着模型层的定义。
那样随着时光的延迟和应用的复杂扩张,就出现了C层膨胀的场馆了。一个控制器的代码可能出现了好几千行的情景。于是乎有人就起来找解决方案来为C层瘦身了。又一个很心痛的谜底是还从未人去想着抽象出M层,而是用了如下方法来化解难题:

  • 客户端和服务器之间交互的数量报文是或不是可以定义出一个个只有属性而从不主意的数码对象啊?那样在拍卖和渲染界面时就不须求和原来的XML或者JSON或者其他的格式报文交互了,只要操作数据对象就好了。于是解决方案就是基于客户端和服务器之间交互报文定义出一个个的数据模型,然后再付出出一套XML或者JSON和数据模型之间互转的解析器来。最后将那个个唯有数据而并未主意的对象数据模型统一置于一个地方,然后给她们定义为M模型层(呼!终于给出模型层的概念了,可是:Are
    you kidding
    me??)
    。那样C层就不会再冒出XML或JSON解析以及一向读取报文的代码了!而是把那有些代码挪到模型层了(大家来看呀,我好不简单应用上了MVC框架了!)
    好了!瘦身第一步成功。不过只是,难点还在啊,我的事情逻辑依然一大片在C层啊,看来MVC这种框架也只是这样啊!根本未曾缓解本身的标题。不行,我无法再用MVC那种框架来开发自己的采纳了,我要另找它法,要延续对C层瘦身。

  • 本人的某个界面和某个业务逻辑是绑定在一块的,那么些界面的呈现是通过调用某个业务逻辑来得以达成的,业务逻辑落成后要平素更新那些界面。那种严峻的调用和创新关系平素就不需求C层的插足。由此得以将那部分界面的更新刷新和业务逻辑的调用绑定在一起,
    二者结合为一个查封而独立的一体化并摇身一变独立的类。那样把那一个类的代码抽离出来了,存放到一个单独的文书夹中。我把这一个有些叫什么好吧?对了就叫视图模型层VM吧!视图模型层中的类定义了一个给外部使用的唯一接口来供C层调用。那样自己终于把一大一部分代码从C层中抽离出来了。我早已成功的贯彻了C层的尤其瘦身,并抽象出了一个视图模型层了!(不过哪个地方好像不对,视图模型层设计到了视图、模型、视图模型层三下边的相互和耦合)
    不过没有涉嫌,反正我的C层进一步瘦身成功了!,我看看还可不得以继续瘦身C层?

V和VM以及M之间的看重关系

  • 自身的过多视图的轩然大波是在C层中处理的,那我是还是不是足以把C层的事件处理也拿出来吗?
    干脆就拿出去呢。可是怎么拿出来吧?于是乎我又不停的查找,终于找到一个叫RAC的东西了,那么些事物好啊,他可以负担处理视图的各样风云,以及可以承受连续的互联网调用。等等。。。
    RAC就是有点晦涩难懂!难以学习,代码难以阅读和调剂。如何是好?
    没有关系,只假若能将C层的代码瘦身那些又算怎么。。。大不断就是多趟一点坑,多搞几回培训就好了。
    嗯! 就这么办,那自己把那有些代码也放入到VM层里面去呢。

    。。。。呼!!!
    C层终于瘦身成功。然后咱们看呀,我的C层里面确实是如何代码也从未了。。。
    它不再处理视图的事件了,因为事件让RAC给处理了、它也不处理视图的基础代谢和作业逻辑的调用了因为让视图模型MV给处理掉了、他也不处理多少的剖析了因为让模型层给替换掉了。嗯。。。。我要给这种没有C层或者不需要C层的框架起个名字,叫什么好呢?
    就叫:MVVM吧。。。
    我的行使能够绝不C层了,然后我就奔走相告。将C层无用大白于天下。。

当真是那样吗?答案是NO!!!

先是我想说的是一个理想的框架中各层次的拆分并不是简约的将代码进行分拣和剪切,**层次的剪切是横向的,而模块的撤并则是纵向的
** 。
那个中涉嫌到了层次之间的耦合性和义务的撤并,以及层与层之间的竞相接口定义和方法,同时层内的宏图也应该有着莫大的内聚性和结构性。而这几个安排的必要并从未在所谓的MVVM中反映出来。

  • 第一要正确的知晓MVC中的M是什么?他是数据模型吗?答案是NO。他的不利定义是工作模型。也就是您所有业务数据和事务完毕逻辑都应有定义在M层里面,而且工作逻辑的落到实处和定义应该和具体的界面无关,也就是和视图以及控制之间一向不其余的关系,它是可以独自存在的,您仍然足以将业务模型单独编译出一个静态库来提需要第三方或者其余系统拔取。在上边经典MVC图中也很清楚的讲述了那点:**
    控制负责调用模型,而模型则将处理结果发送公告给控制,控制再公告视图刷新。由此大家不可以将M简单的敞亮为一个个干燥的唯有属性而尚未章程的数据模型。其实这之中涉及到一个最基本的安插性标准,那就是面向对象的基本安顿基准:就是哪些是类?类应该是一个个具有同等操作和分化性质的对象的架空。
    我想今日其余一个系统之中都未曾出现过一堆唯有数量而从未办法的数据模型的汇集被定义为一个单身而肤浅的模型层来供我们使用呢。**
    大家无法把一个封存数据模型的公文夹来作为一个层,那并不适合横向切分的规则。所以说MVVM里面的所谓对M层的定义就是一个伪概念。

  • 上边我已经认证M层是事情模型层而非数据模型层,业务模型层应该封装所有的工作逻辑的兑现,并且和现实视图无关。大家不可能将一个视图的变现逻辑绑死在一个事务处理逻辑之中,因为有可能存在一个政工逻辑有三种分裂的显现格局,也可能界面彰显会趁机应用升级而生成,可是事情逻辑是相对平稳的。即便是某个视图确实就跟那么些工作是一体耦合的,也不该做强耦合绑定。所以地点所谓的VM这种将视图的显示和事务的拍卖逻辑绑定在一块是可怜稀松的章程,因为这么的安顿形式已经完全背离了系统之中最主旨的显示和贯彻应有分别处理原则。而且那种布署的构思是和分支的理念是违反的。因为他出现了视图和作业的紧耦合和交互双向依赖难题,以及和所谓的M层也要紧耦合的存在。所以说MVVM里面所谓的VM层的概念也是一个伪概念。所谓的VM层那其中只但是是按页面举办的意义拆分而已,根本就谈不上所谓的层的定义。

  • 再来说说事件处理。经典的C层设计的目标是承担事件处理和调度,不论是按钮点击依然UITableview的delegate以及ListView的Adapter都最好放在C层来处理,那也是切合C层最本质的定义:就是C层是一个负责调度和操纵的模块,它是V层和M层的粘合剂,他的职能就是处理视图的风浪,然后调用业务逻辑,然后接过工作逻辑的处理结果通告,然后再通报视图去刷新界面,那就是C层存在的意思。而且系统默许也是按这些法子设计的。而RAC的出现则将那有的的处理给活生生的替代掉了。也就是通过RAC所谓的响应式和触发式那种体制就能兑现将事件的调度处理放在其余地点其余时候都能成功。那样做的目的使得大家得以分散和平解决说代码。但结果现身的题目呢?就是同一个单元调度处理逻辑和效果的打造完全放在了一个地点,但分裂的单元逻辑的又分散在不一致的地方,不可能去分类统一管理和爱惜。由此你无法一下子就知晓某个意义有所调度到底是如何促成以及在哪儿完成的。因为RAC将作用营造和事件处理完全粘合到一个大的函数体内部,并且是代码套代码的格局,那种艺术严重的损坏了面向对象里面的创设和拍卖分离的设计情势理论。更麻烦的是其高昂的就学和保安资产,代码阅读精通困难,以及无处不在的闭包使用。试想一下以此对于一个初大家的话是不是惊恐不已的梦?,一旦出了难点对于保证和代码调试是或不是恶梦?而且使用不当就会现身循环引用的惨重难点。这样一来原本C层一个调度管事人的天职被RAC来接管后,那些处理将变得分散和无序,当大家要做一些联合的田间管理比如HOOK和AOP方面的东西时就变得无法入手了。
    不可以如故不可以认的是RAC在处理一而再调用以及各类响应方面有肯定的优势。一个例证是大家恐怕有连日的三个跟服务器的互连网请求,那时候用RAC进行那种拍卖能便民的化解难题。可是我想说的是当存在那种光景时,大家进一步应该将那种连接的网络调用在M层内部消化掉,而只给C层提供一个简单易行而便利的接口,让C层根本不要求关心那种调用的再三再四性。因而得以说为了把C层的代码给消化掉而引入RAC的体制,不仅没有简化掉系统反而下降了系统的可维护性和可读性。RAC机制根本就不相符用在事件处理中。得天独厚的利用和框架并不在代码的数量,而是完全系统的代码简单易读,各部分职责显明,不难保证的调剂

—— MVVM被引入的根本原因是对M层的错误认识所引起的 ——

MVC中M层落成的守则

说了那么多,可以总括出所谓的MVVM其实并不是一种所谓的框架或者方式,他只是一个伪框架而已,他只是将作用和拍卖按文件夹的措施展开了划分,最后的的结果是系统乱成了一锅粥。毫无层次可言,所具备的绝无仅有亮点是把C层的代码和功力完全弱化了。其实出现那种规划艺术最根本的原故就是没有对M层进行正确的敞亮定义和拆分。那么大家应该怎么样正确的来定义和陈设性M层呢?下边是自己个人认为的多少个准则(也许跟其旁人的见识有出入):

  • 概念的M层中的代码应该和V层和C层完全毫不相关的,也就是M层的对象是不须求依靠任何C层和V层的目的而单独存在的。整个框架的规划最优结构是V层不重视C层而单身存在,M层不看重C层和V层独立存在,C层负责关联二者,V层只负责显示,M层持有数量和工作的实际完成,而C层则处总管件响应以及业务的调用以及通报界面更新。三者之间必然要分明的概念为单向依靠,而不应该出现双向依赖。下边是三层的依靠关系图:

三层之间的单向依靠关系

除非当您系统规划的不等部分都是单向依靠时,才可能便宜的举行层次拆分以及各种层的成效独立替换。

  • M层要形成对业务逻辑完毕的包装,一般工作逻辑最多的是事关到客户端和服务器之间的作业交互。M层里面要成功对应用的互联网协议(HTTP,
    TCP,其余)、和服务器之间交互的数额格式(XML,
    JSON,其余)、本地缓存和数据库存储(COREDATA,
    SQLITE,其余)等有着工作细节的卷入,而且这个东西都无法暴光给C层。所有供C层调用的都是M层里面一个个作业类所提供的积极分子方法来落到实处。也就是说C层是不必要知道也不应当通晓和客户端和服务器通讯所使用的其余啄磨,以及数据报文格式,以及存储方面的内容。那样的功利是客户端和服务器之间的通讯协议,数据格式,以及当地存储的改观都不会影响其余的选择全部框架,因为提需求C层的接口不变,只要求提高和更新M层的代码就足以了。比如说大家想将网络请求库从ASI换成AFN就倘诺在M层变化就足以了,整个C层和V层的代码不变。上边是M层内部层次的定义图:

M层内部的包装层次

  • 既然大家的选拔是一个完全但又分模块,那么业务层内部也理应按功效模块举办布局划分,而不该不难且平面的依照和服务器之间通讯的接口来拓展业务层次的平面封装。我深信有不少人都是对M层的包裹就是简单的根据和服务器之间的互相接口来大约的卷入。上面的二种不一样的M层完结的事体封装格局:

三种不一致的M层封装落成

俺们还足以尤其的对事情逻辑抽象出M层的接口和贯彻两有的,这样的一个便宜是一致的接口可以有区其他贯彻格局,以及M层可以隐蔽格外多的内部数据和章程而不暴光给调用者知道。通过接口和贯彻分离大家还足以在不转移原来达成的功底上,重新重构业务部分的落到实处,同时那种格局也很简单MOCK一个测试完结,那样在进展调节时得以很简短的在真正已毕和MOCK完结之间切换,而不要每一趟都和劳动器端进行交互调试,从而完成客户端和服务器之间的分别支付和调试。上边是一个升任版本的M层体系布局:

基于接口的M层达成

  • M层如何和C层交互的难点也须要考虑,因为M层是不须要精通C层和V层的存在的,那么M层在作业处理完结后怎么去通知C层呢?方法有很两种:
    • 俺们可以为M层的公告逻辑定义Delegate协议,然后让C层去落到实处这个协议,然后M层提供一个delegate属性来赋值处理业务文告的目标。
    • 我们也足以定义众多的NSNotification或者事件总线,然后当M层的工作处理落成后得以发送布告,并且在C层完毕布告的拍卖逻辑。
    • 我们能够用闭包回调或者接口匿名完结目标的款式来落到实处工作逻辑完毕的打招呼功效。而且可以定义出标准:所有M层对象的方法的最终一个参数都是一个标准的如下格式的block或者接口回调:

typedef void (^UICallback)(id obj, NSError * error);

那种格局其实在比比皆是系统中有采用到。我们可以参数考苹果的CoreLocation.framework中的地理地方反解析的类CLGeocoder的定义。还有某些的是在AFN以及ASI中的互联网请求部分都是把成功和破产的拍卖分成了2个block回调,然则此地提出在给C层的异步布告回调里面不区分2个block来调用,而是一个block用2个参数来化解。因为有可能我们的处理中不管成功或者失败都可能有部分代码是形似的,如果分别则会并发重复代码的难点。

MVC中M层完毕的简要举例

最后我们以一个简单易行的用户系列的报到连串来促成一个M层。

1.定义标准的M层异步回调接口:

//定义标准的C层回调block。这里面的obj会根据不同对象的方法的返回而有差异。
typedef void (^UICallback)(id obj, NSError * error);

//这里定义标准的数据解析block,这个block供M层内部解析用,不对外暴露
typedef id (^DataParse)(id retData, NSError * error);

2.概念所有M层业务类的基类,这样在通用基类里面我们得以做过多处理。比如网络层的合并调用,加解密,压缩解压缩,大家还足以做AOP和HOOK方面的拍卖。

     @interface  ModelBase

           //定义一个停止请求的方法
           -(void) stopRequest;
           /**
             *定义一个网络请求的唯一入口方法
             * url 请求的URL
             * inParam: 入参
             * outParse: 返回数据解析block,由派生类实现
             * callback: C层通知block
             */
           -(void) startRequest:(NSString*)url  inParam:(id)inParam outParse:(DataParse)outParse  callback:(UICallback)callback;
     @end

3.定义一个用户类:

    @interface  ModelUser:ModelBase

        @property(readonly) BOOL isLogin;
        @property(readonly) NSString *name;

       //定义登录方法,注意这个登录方法的实现内部可能会连续做N个网络请求,但是我们要求都在login方法内部处理,而不暴露给C层。
       -(void)login:(NSString*)name  password:(NSString*)password   callback:(UICallback)callback;
        //定义退出登录方法
       -(void)logout:(UICallback)callback;
    @end

4.概念一个M层总种类统类(可选),那几个类可以是单例对象:

    @interface ModelSystem:ModelBase

     +(ModelSystem*)sharedInstance;

    //聚合用户对象,注意这里是readonly的,也就是C层是不能直接修改用户对象,这样保证了安全,也表明了C层对用户对象的使用权限。
    @property(readonly)  ModelUser *user;  

    //定义其他聚合的模块

    @end

5.在C层调用用户登录:

  @implementation LoginViewController

    -(IBAction)handleLogin:(UIButton*)sender
   {
        sender.userInteractionEnabled = NO;
        __weak LoginViewController  *weakSelf = self;
       [[ModelSystem sharedInstance].user  login:@"aaa" password:@"bbb"  callback:^(ModelUser *user, NSError *error){

        if (weakSelf == nil)
               return;
       sender.userInteractionEnabled = YES;
       if (error == nil)
       {
              //登录成功,页面跳转
       }
       else
      {
            //显示error的错误信息。。
      }}];

   }

   @end

可以看出地方的C层的局地格外简单明了,代码也易读和易于驾驭。同时我们还察看了C层跟本不须求精晓M层的登录达成到底是如何请求网络的,以及呼吁了多少个互连网操作,以及用的哪些协议,以及哪些数据报文格式,所有的那所有都封装在了M层内部贯彻了。C层所要做的就是简约的调用M层所提供的艺术,然后在callback中通报界面更新即可。整个C层的逻辑也就是几十行就能搞定了。

切切实实的模型层设计方式请参见M层的设计


欢迎大家关切自我的github地址,关注欧阳堂弟2013,关怀本身的简书地址:http://www.jianshu.com/u/3c9287519f58