美文网首页iOS 实用技术iOS开发实用工具
论MVVM伪框架结构和MVC中M的实现机制

论MVVM伪框架结构和MVC中M的实现机制

作者: 欧阳大哥2013 | 来源:发表于2017-07-01 10:11 被阅读6277次

    目录

    一直都有人撰文吹捧MVVM应用开发框架,文章把MVVM说的天花乱坠并且批评包括iOS和android所用的MVC经典框架。这篇文章就是想给那些捧臭脚的人们泼泼冷水,虽然有可能招致骂声一片,但是目的是给那些刚入门的小伙伴一些参考和建议,以免误入歧途。同时也给那些深陷其中不能自拔的小伙伴们敲敲警钟,以免其在错误的道路上越走越远。

    ------ MVVM并非框架,而只是简单的文件夹分类 ------

    MVVM被引入的前因后果

    大概是在2010年左右移动端开发火了起来,起初是iOS,Android, WinPhone三个大平台竞争,后来后者退出了角逐,变成了二分天下。从应用体系结构以及为开发者提供的框架体系来看,两个平台都是推出了经典MVC三层结构的开发方式,这三层所代表的意义是模型、视图、控制。这个开发框架的初衷其实也很简单:视图负责展示和渲染,模型负责业务逻辑的实现,控制负责调度视图的事件以及业务逻辑的调用以及通知视图的刷新通知。 三部分松散耦合,各司其职。下面是经典的MVC框架结构:

    MVC框架图

    一个很可惜的事实是不管是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层?

    MVVM各层的依赖关系
    • 我的很多视图的事件是在C层中处理的,那我是不是可以把C层的事件处理也拿出来呢? 干脆就拿出来吧。但是怎么拿出来呢?于是乎我又不停的寻找,终于找到一个叫RAC的东西了,这个东西好啊,他可以负责处理视图的各种事件,以及可以负责连续的网络调用。等等。。。 RAC就是有点晦涩难懂!难以学习,代码难以阅读和调试。怎么办? 没有关系,只要是能将C层的代码瘦身这些又算什么。。。大不了就是多趟一点坑,多搞几次培训就好了。 嗯! 就这么办,那我把这部分代码也放入到VM层里面去吧。

      。。。。呼!!! C层终于瘦身成功。然后大家看啊,我的C层里面真的是什么代码也没有了。。。 它不再处理视图的事件了,因为事件让RAC给处理了、它也不处理视图的刷新和业务逻辑的调用了因为让视图模型MV给处理掉了、他也不处理数据的解析了因为让模型层给替换掉了。嗯。。。。我要给这种没有C层或者不需要C层的框架起个名字,叫什么好呢? 就叫:MVVM吧。。。 我的应用可以不要C层了,然后我就奔走相告。将C层无用大白于天下。。

    真的是这样吗?答案是NO!!!

    首先我想说的是一个优秀的框架中各层次的拆分并不是简单的将代码进行归类和划分,层次的划分是横向的,而模块的划分则是纵向的 。 这其中涉及到了层次之间的耦合性和职责的划分,以及层与层之间的交互接口定义和方式,同时层内的设计也应该具有高度的内聚性和结构性。而这些设计的要求并没有在所谓的MVVM中体现出来。

    MVVM据说是来源于微软的数据视图的双向绑定技术。也就是有一个VM的类来实现数据的变化更新视图,视图的变化更新数据的处理,整个过程不需要再单独编码去处理。这个技术就和早年MFC里面的DDX/DDV技术相似。MVVM只是一种数据绑定技术的变种而不足以称为框架。框架中的层的要素要具有职责和功能的属性。就MVVM中所定义的M只能理解为纯数据。纵观整个iOS和android中的所有系统框架库都没有出现过让一批数据结构组成一个层的概念。即使如所谓的存储层也是数据库和表以及数据库引擎三者的结合体为一层。 其实之所以说控制器膨胀根源在于我们的手写布局视图在控制器中完成这里占用了非常多的代码, 业务处理和实现也在控制器中完成。苹果和Google已经给出了通过SB和XML来实现视图的构建。至于复杂的业务逻辑也完全可以通过拆分为多个子视图控制器或者多个Fragment 来完成。请问如果一个设计的足够好的C层,何来膨胀这么一说!

    • 首先要正确的理解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

    相关文章

      网友评论

      • 时光浅影:楼主是否有此架构的相关开源项目
      • MR_詹:确实M层不应该只是一个数据模型,还可以将一些功能赋予给它,这样具有“层”的概念,讲的很专业。在开始接触MVC的时候就一直都有这个疑惑,自己也实践过,后来在网上看了很多MVC的模型后慢慢的淡忘了这个疑惑,直到现在还一直就将M当做模型的功能,感谢带给了更多的思考
      • 好尴尬11:MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,C存在的目的则是确保M和V的同步,一旦M改变,V应该同步更新。 MVVM由M,V,VM组成,等同于MVC。VM就是viewModel,专注于业务逻辑处理,也就是保持M和V的同步。而V则是View,ViewController的统称,专注于ui呈现,UI生命周期,以及Ui跳转,M就是基本的数据结构model,小到一个数字,一个字符串,大到一个自定义对象。之所以ios开发中MVC架构导致胖C存在,那是因为苹果这套MVC本身就有误导性,大家都以为viewcontroller就是MVC中的C,我觉得这是错误的。然后RAC作为一个辅助的绑定工具为VM层的开发提供了便利,即使没有RAC,一样有别的技术可以完成。当然RAC不仅仅只是绑定,更是一种函数式编程思想。所以说在使用MVVM开发过程中,不要为了RAC而RAC,非得往函数式编程里面钻,简单的RAC就能满足MVVM开发,也不要为了为viewcontroller瘦身就把所有逻辑都往VM里塞,业务逻辑就是业务逻辑,UI逻辑就是UI逻辑,二者不可混为一谈。
        欧阳大哥2013:@好尴尬11 是的,很可惜包括很多人也是这样认为的,以为MVVM就一定要和响应式混合起来才够高大上,包括美团目前在建设的框架也是如此。 响应式包括函数式以及常规的编程思想就如算 1 + 1 一样,常规的写法是: 1+1, 响应式的写法是: 1 - (-1)
      • 阿文灬:在学习Javaweb的时候,m层就包括model、DAO、Service;view使用h5或jsp;c层则处理界面的事件
        欧阳大哥2013:@小蚊子叮迎行 正解
      • 白屏:MVVM火了一下我感觉更多是因为跟风吧,有些人把C中的网络请求抽到vm中就认为用上MVVM了,那我抽个请求类不是更好逻辑更集中;有些人把model作为纯数据模型,然后给VM,VM再对数据加工一下给view直接使用就认为是MVVM了,那我重写属性get方法不是更好么;至于RAC,我感觉主要就是解决层多了,事件传来传去不方便的问题,我用了下这个框架感觉更可怕,不给后续接收你项目的人活路了,还不如写一大堆代理通知呢!可能我功力还差得远吧,并不能体会到MVVM特别多的好处,抽那么点代码没有多少好,逻辑变得更复杂,而且整个项目有些地方用MVVM,简单界面用MVC,更怪了
      • TEASON:这篇文章现在才看到,我之前就觉得mvvm必须和rac结合才行得通, 否则是多余的, 然而我现在依然不用mvvm
        TEASON:@欧阳大哥2013 双向绑定用过rac之后,不可能再会用其他方式了. 实战我认为mvvm+rac不如mvc+rac. 个人觉得, 在我业务层已经有足够的能力抽象所有业务的时候,mvvm的使用场景完全多余了.
        欧阳大哥2013:@TEASON MVVM并不等价于RAC。而只是刚好RAC是一种响应式的框架,他对于实现视图和模型之间的绑定和通信提供了一种非常好的解决方案。其实MVVM也可以通过KVO或者定义接口回调的方法来完成这种双向通知的能力。
      • 那太无情:只是胖廋model引出的问题吗?
        欧阳大哥2013:@那太无情 胖瘦是一个因素 还有视图和控制器的耦合性是一个因素
      • Yaanco:ModelBase中startRequest:方法是不是需要添加一个请求方式的参数呀,比如GET或者POST
        Yaanco:@欧阳大哥2013 ok
        欧阳大哥2013:@单线程 是的。
      • 迷了jiang:好文啊,理解很深刻!
      • jameiShi:美团不是做了 mvvm 的好多实践么
      • 鸟人扎墨客:ModelUser 要在什么地方初始化呢
        鸟人扎墨客:@欧阳大哥2013 谢谢指教:smile: 很好的文章,值得多读几遍
        欧阳大哥2013:@鸟人扎墨客 最佳的实践就是在模型层内部建立和初始化。控制层总是使用者的角色。你可以在登录服务内部创建用户对象,也可以在系统初始化时创建用户对象
      • KeepMoveingOn:请教下博主,在你所画的伪MVVM的第一幅图中,按你的意思VM的作用只是原本C中的业务代码剥离,那为什么第一幅图中V和VM还是双向关联?不是应该处理完业务逻辑回调到C中,图中VM应该不持有V,而是C进行V的持有
        9e72b5a52cd1:请问一下你说的是名为“MVVM各层的依赖关系”这张图吗
        欧阳大哥2013:@KeepMoveingOn 感谢你的提醒 是我画错了 这就改过来
      • leeJessie:我现在就是这样定义MVC的,M不只是Model的意思,还可以是Manager的意思,比如定位管理,我可以直接一个方法加闭包回调就知道了经纬度甚至省市区编码都知道了,但是这个对C来说是不知道的,代码也容易阅读
      • 敌敌味丶:之前那个一千个赞的感觉说得我都听不懂了,感觉还是你写的清楚
      • 郭小弟:首先表示一下对大神的膜拜,看了您的文章感觉自己对MVC的理解有多渣,一直把模型当做数据模型,从来没有用模型做过业务层面的尝试,看了您的文章果断在项目中使用,谢谢大神,期待您更多的架构分析
        欧阳大哥2013:@码上有钱 我还有一篇姐妹文章的 你可以阅读一下
      • 清河湾:深度好文,受教了:smile:
      • huifeidelele:作者你好,在此请教几个问题。我在Service类中回调数据模型对象XXXModel(项目后台多返回Json字典或本地存储,未做逻辑处理)和Error。这里有2个小问题:第一个问题在本页面使用的数据可能是XXXModel的一部分数据的组合或者逻辑处理结果,数据要给View使用,要经过比较多的“表示逻辑”来处理(如果没什么“表示逻辑”处理,那么就直接在Controller里面传给View使用就好了)。问题是这些“表示逻辑”是应该在Service中继续处理,然后赋值给Service类中的实例变量;还是再新建一个ViewModel类将“表示逻辑”处理,赋值给ViewModel类中的实例变量。最后将处理结果经由Controller给View使用。第二个问题由于上述的Service类回调的数据模型对象XXXModel(未做逻辑处理)可能需要接下来使用或作为参数往下传递,我们让Controller、Service或ViewModel哪个类持有好点,还是说有其他的考虑方案?
        欧阳大哥2013:@huifeidelele 你所谓的表示逻辑是完全可以在你的xxxxmodel的一个只读属性中封装。数据模型也可以有方法。另外肯定是控制器持有模型数据了。我想不通为什么要再加一个VM出来做什么。
      • cp__kong:想问下大神,我拿model去渲染view,在view中包含model的成员变量,然后在set方法里处理model,这是是不是使得v依赖于m?
      • JC一直在路上:一直把M层理解错了,看完此文受益匪浅!
      • ifelseboyxx:真是相见恨晚,受益匪浅,服!
      • xiari1991:有些没看懂,求证下:

        1. M 层业务逻辑的接口和实现分离是不是说的用继承的方式定义一个基类,声明一些接口,由子类去实现。当然也有可能是用协议
        2. M层的数据请求: 参数,服务器地址。是不是都是从C层传递的呢?如果是固定的话,是不是都可以写到M层?
        3. M层的数据交由C去管理,如果操作V需要M变化也是直接在C层修改的吧
        4. 我可不可以将登录、注册,更新用户资料,获取验证码等都作为同一个业务层?
        5. 数据模型的复用问题,如果不同业务层都有商品这个数据模型,后台返回的商品信息数据结构可能不太一样,应不应该复用同一个数据模型呢?
        欧阳大哥2013:很好的问题:
        1. 在iOS中所谓的协议其实可以理解为接口,至于接口和实现分离的技术在设计模式里面有很多的方法来介绍如何实现。
        2.M层实现了对网络请求的封装,因此他提供给外部的接口要屏蔽掉这些差异,因此他需要把和服务器之间请求的参数以及服务器地址进行封装和转化,对于C层来说他说面对的就是一个个普通的类方法,C层看不到任何M层如何请求网络已经如何封装数据报文的痕迹。
        3.最佳的实践是由C层去驱动M层的数据变化,当然如果特殊情况下V层也是可以驱动M层数据变化的。
        4.登录、注册、更新用户资料等从实践来说都是属于一个用户类或者用户管理类的行为,因此可以将这些操作放到用户类或者用户管理类里面去,而获取验证码则一般和用户无关,你可以将这个操作放到比如一个全局的系统类里面去。这其实就是面向对象设计里面如何来构建类的一些原则和方法。
        5. 这是一个很好的问题,某些数据模型报文可能会因为接口的差异而出现字段的一些微小差异,个人的建议是我们处理成相同的数据模型。
      • Cedric_Jc:m 一旦理解为业务模型... 好多就通了... 从面向对象的角度来考虑层次结构和接口设计... 突然一下清楚好多。
      • talon23333:大神写的很好,收获贼大!但是我有个问题,Model当作业务模型而不再是数据对象,那我觉得还是有数据对象比较好,那这时数据对象应该怎么处理呢?感觉再把Model当作数据对象不太合理,这时候应该再新建数据对象吗? 或者说数据对象是否有存在的必要?
        talon23333:@欧阳大哥2013 谢谢大佬!风里雨里,周末等你:sunglasses:
        欧阳大哥2013:@NSLOGSB 好问题 数据对象是可以存在的。他还是放到模型层 业务模型是包括这些数据模型和业务类的。这周末我会有另外一篇文章详细介绍模型层的设计方法 敬请期待吧
      • SuChAChAoS:说的很好 刚入职的新手IOS开发 受益匪浅

        有个问题:jsonmodel框架下 Model只能是纯数据 这样M层该怎么设计呢?
        SuChAChAoS:@欧阳大哥2013 OKOK 懂了 谢
        欧阳大哥2013:你所说的model还是局限在数据模型上。记得一点MVC里面的M是业务模型。其中的数据模型可以理解为一个个数据结构,数据模型是业务模型操作的数据而已。
      • 別來無恙錒:感谢,收获很大
      • 西木柚子:请问,c获取到数据后如何传递给view显示,按照文中的观点,v和m不能产生依赖。那么v的数据要给view显示,要么直接操作view的控件填入数据,要么传字典给view显示。但是这两种方法都比较蛋疼,有什么好的解决方案吗?望指教
        欧阳大哥2013:@西木柚子 这个问题很好解决 对于cell这种如果和某个模型有强耦合的场景时。可以在cell上设置一个模型属性。这样更新视图时 只需要设置这个视图的模型属性就好了。然后你重写视图的模型属性设置方法。这种机制也是可以的
        西木柚子:@欧阳大哥2013 因为有时候数据展示这块也有逻辑,并不是简单的数据填充,那么这些逻辑就会转移到controller里面。如果数据过多的话,按照你说的给view写一个category方法,那么这些数据如何传递过去?传参数肯定不现实,太多了。传字典的话,还要取出来,而且key都是硬编码,没有字段检查,难免会出错。
        不过如果直接把model直接传递给view的话,又会导致二者耦合。所以这两种方案就是一个取舍问题。

        我看大多数mvvm的实现就是简单的把c的业务逻辑搬到vm中,这大概就是你文中所说的vm只是对胖c的简单的功能切割。
        其实我觉得这是大多数人对mvvm的误解,首先正确的mvc实现方法应该如你所示,c就是一个粘合剂,负责协调v和m,还有和其他c的跳转,不应该把业务逻辑也丢到c里面去处理。m是业务层,实现业务逻辑,那么随着业务的增多,m可能会膨胀,其中一个膨胀点就是数据加工问题(很多时候从服务器端获取的数据并不能直接展示,需要经过一系列处理),这个时候可以从m中分离出来vm,专门来做数据处理然后给view去显示,可以参考这篇文章中关于mvvm的解释(https://casatwy.com/iosying-yong-jia-gou-tan-viewceng-de-zu-zhi-he-diao-yong-fang-an.html)
        在我看来vm是将模型层中的数据与复杂的业务逻辑封装成属性与简单的数据同时暴露给视图,让视图和vm中的属性进行同步。

        不知你对此有什么看法
        欧阳大哥2013:@西木柚子 推荐是操作view控件填写数据。但是不知道这种方法你遇到的问题是什么。 如果数据多你可以为view写一个扩展方法之类的来实现数据填充
      • 海盗军长:请教一下,按你写的网络请求放到M层,那么菊花加载控件的显示是放到C层来调用吧,你是通过什么方式去回调C层的?

        -(void) startRequest:(NSString*)url inParam:(id)inParam outParse:(DataParse)outParse callback:(UICallback)callback

        是这里的callback吗?
        欧阳大哥2013:@Nathan_c14f 具体问题具体分析 因为用户对象很少 而且一般系统只有一个用户对象。而且这个用户对象一直会存在 所以一个全局对象是最好选择。至于其他如果数量众多当然不适合单例或者全局了
        海盗军长:还想请教个问题,
        @interface ModelSystem:ModelBase

        +(ModelSystem*)sharedInstance;

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

        //定义其他聚合的模块

        @EnD

        -------这里的ModelUser *user是只读的,那么它是在ModelSystem的初始化时创建吗,如果有很多个这样的模型对象,而且一直持有的话,会占用很多内存吧?
        欧阳大哥2013:@Nathan_c14f 是的. 具体的业务会封装startRequest方法。然后再调用封装方法前开启菊花,然后再callback中销毁菊花。
      • zkhCreator:感谢大佬的文章,思路瞬间清楚了。膜拜
      • 不喜欢说话的小张同学:我们在日常开发中使用MVVM,主要原因是为了c层瘦身,拆分不同类型的逻辑。我们认为vm层是数据处理和网络请求层,以tableview举例,我们把datasource和cellHeightSource属性放在了vm中,在方法上,我们把网络请求和数据处理工作放在了这一层,给c层只预留相关api。M层,本质上还是以属性存储为主,另外把相关数据类型的转换和处理放在这一层.(比如不同数据类型的转换,时间输出格式的转换),View放的是视图的相关操作,包括动画,和相关数据填充方法.C层作为粘合剂负责关联其他所有层,C层作为粘合剂负责关联其他所有层,也放置一些其他的逻辑如跳转逻辑等.总体来说,c层将vm层的数据(M),填充到v层上,.想请大神请假我们这种方案的优劣
        不喜欢说话的小张同学:@断片记忆 不全是,毕竟代理还在c层
        断片记忆:“以tableview举例,我们把datasource和cellHeightSource属性放在了vm中”
        按你这句话所说,那就是VM直接给V填充数据咯?和C层就无关联了吧..
      • JeffWei:写的好,最近也在瘦身项目,刚开始也是理解不透,很多逻辑都是写在了C,导致C臃肿,后来发现很多V的显示,用model来控制既可以使C代码阅读性强,又能简化代码,于是想到了这有没可能是MVVM的思路,然后看了很多相关的文章,一直怀疑MVVM其实本身就是MVC,只是最早的理解错误,把M的事情都放在了C.....感觉现在的MVVM其实完全就是MVC,很多人理解M只是纯数据而已,导致C臃肿...
      • a5a2b92ecd85:哈哈
        之前就看过你写的东西
        厚积薄发吧
      • 无风起个浪:看完了,总感觉我是一个假iOS程序员,大神收下我的膝盖
      • 顾泠轩:同求小demo
      • DongLei:作者多MVC理解的很透彻,但MVC的问题是把网络请求,数据操作,业务操作等都放入M里面,最终的问题是M过于臃肿,可读性降低不易维护。
        MVVM本质是MVC这点大家都没有疑义,只不过是对M做了更下细化的拆分,
        MVVM的提出和使用,是有它的优点和存在价值的:可以解决M层过于臃肿的问题。
        欧阳大哥2013:@DongLei 首先希望你明确的是MVVM并非是一种所谓的新技术。就像现在所谓的函数式编程其实二十多年前STL已经玩的很熟烂了。现在的概念只不过是新瓶旧酒了。如果你的开发经验在10年以上你就更加的深有体会了。另外用什么方法和圈子无关。技术是通用的 就看是否合适在应用上。很多开发者在做移动互联网前做过其他系统开发。自然而然的就会用老的经验去改造新的东西。我所表达的恰恰是希望人们接受新技术和系统提倡的开发方法,而不是去全新创造一套不管是否合适的东西去改造人家已经成熟的开发方式。
        DongLei:C的臃肿,肯定是开发者对MVC的理解不够到位,VM的提出确实将Model的部分职责单独抽出,某种程度上确实可以优化一些胖M层的问题,但不一定是最好的方式。
        MVVM最早被微软提出,是否是为了解决C层臃肿的问题还需要考证,也许只是像部分开发者认为MVC会造成C的臃肿一样是一个错误的解读。
        理解MVVM的思想要逛的是微软的圈子,而不是iOS圈。
        以下是从微软开发者文档中截出的原文,明确提到,框架可以避免对M层代码进行任何重大更改,但未提到可以对C层进行瘦身。
        The Benefits of MVVM
        MVVM enables a great developer-designer workflow, providing these benefits:
        During the development process, developers and designers can work more independently and concurrently on their components. The designers can concentrate on the view, and if they are using Expression Blend, they can easily generate sample data to work with, while the developers can work on the view model and model components.
        The developers can create unit tests for the view model and the model without using the view. The unit tests for the view model can exercise exactly the same functionality as used by the view.
        It is easy to redesign the UI of the application without touching the code because the view is implemented entirely in XAML. A new version of the view should work with the existing view model.
        If there is an existing implementation of the model that encapsulates existing business logic, it may be difficult or risky to change. In this scenario, the view model acts as an adapter for the model classes and enables you to avoid making any major changes to the model code.

        对于任何新技术要持有开放的心态,同时持有理性分析和严谨考究的态度,是否适合自己的业务,是否能深入理解并熟练运用需要根据实际情况进行判定。
        没必要被过分的推崇,也没必要互黑。
        欧阳大哥2013:@DongLei 首先VM并没有解决M层的臃肿问题 设计好的M层的体系里面 网络和其他处理事是有一个总引擎在处理的。并不存在分散的逻辑 而且这种处理逻辑是可以层次化的 这就是为什么我要提出业务基类的startrequest的方法的原因。所以不存在M层臃肿的问题。你可以看到在M层内部其实也是有层次结构的。而且很容易剥离出网络和真实业务部分。MVVM的应用场景是解决所谓C的膨胀 而非M的膨胀。
      • Life淡淡:完全同意。
      • 0c250eedc44b:小白提问:请问多写一个 M层总体系统类 有什么好处呢?
        欧阳大哥2013:@MC_0fc9 好问题,一个总控类的目的是防止分散管理。另外如果你是通过接口编程那么这个类就具有抽象工厂的作用。另外假如你的系统有A,B ,C三个管理子模块 那么我们也可以建立一个系统业务类来做总体控制和调度 以及用它来保存一些全局的业务参数和设置
      • R0b1n_L33:相见恨晚
        1.VM对Fat C中业务逻辑仅仅是单纯切割
        2.M层代表业务模型而非数据模型
        3.RAC侵占了C层
        4.各层之间需要单向引用才能真正解耦
        5.层次横向,模块纵向
        6.自定义Model只是数据结构而不是一个层
        7.网络和数据库应该是M层中的子模块而不是一个单独分层
        以上每一个论点都切中要害 针砭时弊
        论据看得出都是具有实战经验的总结
        M层的设计,M层与C层的交互设计 皆堪称教科书
        这篇是我今年读到的第一篇几乎找不到操作漏洞的架构设计好文
        欧阳大哥2013:@cp__kong 是的 但这样并不会破坏单向依赖。
        cp__kong:大神,我想问一个问题,就是mvc单向依赖,那么我封装的http类拿到数据,解析成model模型,我再去渲染view,,我再view里面包含这个模型,在set方法里传值,这样是不是意味着view其实依赖了model
        欧阳大哥2013:@ljysdfz 哈哈 太过奖了。你能精炼出我想要表达的意思。说明就是相见恨晚。
      • 张小小白:有这种的小demo吗
      • 漠川:受益匪浅,我想问下,MVP你是怎么理解的
        欧阳大哥2013:@漠川 其实MVP是MVC的变种,因为MVP更加严格 。视图和模型之间完全隔离,所有处理都在P中进行。一个经典的例子就是uitableviewcell 是否可以持有一个数据模型 MVC是可以持有并且cell 根据数据模型来填充视图 。而MVP则cell不持有数据模型 而是在P中用数据模型来填充cell 中的视图。如果V代表房客 M代表房东,那么C就是中介,P就是二房东。
      • 零邀国度:写的好,看来我之前的Model层只属于平面M层,还有改进的地方,之前总感觉的自己的项目架构有缺陷,最近正想着优化,看到了你的文章,受益匪浅.
      • 群星陨落:说得好,所谓的 MVVM 其实只不过是 MVC 的一个并不怎么高明的实现方式而已

      本文标题:论MVVM伪框架结构和MVC中M的实现机制

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