美文网首页iOS开发新发现iOS技术笔记收录iOS开发经验总结
实际项目中的MVVM(积木)模式第二章:model--如何高效利

实际项目中的MVVM(积木)模式第二章:model--如何高效利

作者: 数聚宝土豪君 | 来源:发表于2016-06-01 18:04 被阅读1480次

    <em><strong>“不为炫技而炫技,不为架构而设计架构,只为写出一个接地气、通俗易懂的使用方法”</strong></em>

    (注:了解整个设计模式体系请查看我上篇文章实际项目中的MVVM(积木)模式:序章

    这篇文章讲解Model和ViewModel。

    本着易扩展、易理解的前提,讲解中Model和ViewModel都用最基础的方法和易理解的思维图。

    为何为何会将View和ViewModel合并在一起讲解?为何我会将有些文章说的的网络层与数据层合并一起叫数据层?

    很简单,我们需要明白一个道理,无论是网络请求还是本地缓存,本质上都是传递数据;因此,我们要做的就是将各种来源的数据通过数据加工(ViewModel)形成统一的格式(Model)再通过一个统一的接口传递给需要的地方。我所讲的数据层,我把这形象地叫为数据工厂。

    数据工厂

    首先,我们先开始说说:Model。
    <strong><h4>一、Model--项目的信息承载与传递者</h4></strong>
    一个多人协同开发的项目,保证数据结构的一致性和稳定是很有必要的。而Model则是很好的实现了这一需求。
    首先,我们先通过三个大的方面将字典与Model作一个比较,更直观了解Model的特点。

    <strong>1、字典与模型的比较</strong>

    <strong>a、取值:</strong>字典会因为没有取值的这个key或者这个错误的key刚好是这个字典中其他类型的值对应的key(因输入错误等原因),通过这个key取出来的值为nil或者其他类型的值,如赋值给label之类的文字控件,可能会导致程序崩溃,而Model不会出现这样的问题;
    <strong>b、数据展示:</strong>字典无法再不改变数据源的前提下,改变数据的格式,而Model则可以通过get方法实现这个需求,保证了数据的原始性和可变性;
    <strong>c、后期维护:</strong>字典每个key的具体含义和有多少key要通过接口文档去了解,而Model体现在具体的属性和每个属性的备注上;
    可能有同学会问建立Model一个个去复制粘贴属性好麻烦,还有像数据缓存之类还要一个个写解挡归档好麻烦呢/(ㄒoㄒ)/~~
    这么多好的优点的前提下,这几个小麻烦肯定会通过方法解决噻,且看下面:

    <strong>2、解决Model的一些小麻烦</strong>

    <strong>a、如何快速建立Model:</strong>这里有份代码,可以将网络请求下来的字典里的key在控制台打印成Model里的属性格式哦。(打印效果在代码块下面)

    - (void)writeInfoWithDict:(NSDictionary *)dict
    {
        NSMutableString *strM = [NSMutableString string];
        
        [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
            //  NSLog(@"%@,%@",key,[obj class]);
            
            NSString *className = NSStringFromClass([obj class]) ;
            
            if ([className isEqualToString:@"__NSCFString"] | [className isEqualToString:@"__NSCFConstantString"] | [className isEqualToString:@"NSTaggedPointerString"]) {
                [strM appendFormat:@"@property (nonatomic, copy) NSString *%@;\\\\n",key];
            }else if ([className isEqualToString:@"__NSCFArray"] |
                      [className isEqualToString:@"__NSArray0"] |
                      [className isEqualToString:@"__NSArrayI"]){
                [strM appendFormat:@"@property (nonatomic, strong) NSArray *%@;\\\\n",key];
            }else if ([className isEqualToString:@"__NSCFDictionary"]){
                [strM appendFormat:@"@property (nonatomic, strong) NSDictionary *%@;\\\\n",key];
            }else if ([className isEqualToString:@"__NSCFNumber"]){
                [strM appendFormat:@"@property (nonatomic, copy) NSNumber *%@;\\\\n",key];
            }else if ([className isEqualToString:@"__NSCFBoolean"]){
                [strM appendFormat:@"@property (nonatomic, assign) BOOL   %@;\\\\n",key];
            }else if ([className isEqualToString:@"NSDecimalNumber"]){
                [strM appendFormat:@"@property (nonatomic, copy) NSString *%@;\\\\n",[NSString stringWithFormat:@"%@",key]];
            }
            else if ([className isEqualToString:@"NSNull"]){
                [strM appendFormat:@"@property (nonatomic, copy) NSString *%@;\\\\n",[NSString stringWithFormat:@"%@",key]];
            }else if ([className isEqualToString:@"__NSArrayM"]){
                [strM appendFormat:@"@property (nonatomic, strong) NSMutableArray *%@;\\\\n",[NSString stringWithFormat:@"%@",key]];
            }
            
        }];
             NSLog(@"\\\\n\\\\n%@\\\\n",strM);
    }
    

    看下图打印效果,那么打印出来的效果大家知道了吧,直接复制粘贴就OK啦:

    打印效果

    <strong>b、怎么解决繁琐的解挡归档呢</strong>

    在BaseModel(Model的基类)中写一个统一的解挡、归档,这里就要用到runtime中非常有用的两个方法:

    class_copyIvarList(Class cls, unsigned int *outCount) //遍历该类成员变量列表
    
    ivar_getName(Ivar v) //获取该类某个成员变量的名字
    

    那么具体怎么在基类写一个,所有适用呢,且看下面:

    - (void)encodeWithCoder:(NSCoder *)encoder
    {
        unsigned int count = 0;
        Ivar *ivars = class_copyIvarList([self class], &count);
        
        for (int i = 0; i<count; i++) {
            Ivar ivar = ivars[i];
            const char *name = ivar_getName(ivar);
            // 归档
            NSString *key = [NSString stringWithUTF8String:name];
            id value = [self valueForKey:key];
            [encoder encodeObject:value forKey:key];
        }
        
        free(ivars);
    }
    
    - (id)initWithCoder:(NSCoder *)decoder
    {
        if (self = [super init]) {
            unsigned int count = 0;
            Ivar *ivars = class_copyIvarList([self class], &count);
            for (int i = 0; i<count; i++) {
                Ivar ivar = ivars[i];
                const char *name = ivar_getName(ivar);
                // 解档
                NSString *key = [NSString stringWithUTF8String:name];
                id value = [decoder decodeObjectForKey:key];
                [self setValue:value forKey:key];
            }
            free(ivars);
        } 
        return self; 
    }
    

    至此,所有继承于这个Model基类的Model都自动实现了解档归档的方法。
    既然解决了建立Model的一些小麻烦,我们就来构建一个项目中标准的BaseModel(基类模型)。

    <strong>3、何为基类Model建立要求?</strong>

    <strong>a、数据格式读取统一与写入统一;</strong>
    <strong>b、模型属性值可批量修改;</strong>

    这样才能保证在“千奇百怪、朝令夕改”的数据源中,进入到这个项目体系后,面向业务工程师的时候,是统一整齐的标准模型,然后业务工程师才会在这个基础之上扩展其他子Model。
    其中读写统一则是通过上面的解档归档解决,而模型属性值批量修改则是通过李明杰大神的MJExtension(这个三方库可以在不用继承其他Model前提下使用,保证了Model独立性),具体代码在BaseModel如下:

    - (id)mj_newValueFromOldValue:(id)oldValue property:(MJProperty *)property
    {
        if ([property.name isEqualToString:@"buy_price"]|| [property.name isEqualToString:@"buy_sale_price"]||[property.name isEqualToString:@"info_price"]||[property.name isEqualToString:@"sale_price"] ) {
            if (oldValue == nil || [oldValue isKindOfClass:[NSNull class]])
                return @"价格面议";
        }//可将所有模型在生成时将涉及相应字段的值变成“价格面议”
         else if (property.type.typeClass ==[NSString class]) {
            if (oldValue == nil || [oldValue isKindOfClass:[NSNull class]])
                
                return @"--";//可将所有模型在生成时将string类型从nil或Null变成“--”
        } else if (property.type.typeClass == [NSArray class]) {
            if (oldValue == nil)
                return [[NSArray alloc] init];//可将所有模型在生成时将array类型从nil变成[[NSArray alloc] init]
        }
        
        return oldValue;
    }
    

    以上代码举了部分替换的例子,目的是告诉大家通过这方法可以在数据源传给客户端是非友好数据的时候,我们客户端能够进行处理,给业务工程师一个友好的数据。这对数据工厂这个模式来说是非常有必要的。
    <strong>至此,BaseModel就只有写两个方法:解档与归档 以及 属性值批量修改。因为我们始终要明白:Model是来保证整个项目架构数据结构的一致性和稳定性。那么继承这个BaseModel的子Model就都具备了面向业务工程师友好数据的特点。</strong>

    那么如何正确使用子Model呢?
    我举两个例子:

    <strong>1、如何将模型源数据的时间 20161010 变成 2016-10-10(正确使用get方法)</strong>

    - (NSString *)pub_date{
        
        return [_pub_date formatDateString];//formatDateString是NSString的Category
        
    }
    

    这样的好处有两方面:一方面,业务工程师不会修改源数据,保证了源数据的安全性;另一方面,类似formatDateString的方法,是通过Category(也就是我后面要说的工具类)使用的,保证了代码的低耦合性。

    <strong>2、使用子Model分离数据的一个例子(这个例子感谢我的同事 张尔柏 同学提供,展示这个例子主要目的是为了表达子Model其中一个在cell样式数据分离的作用,可能会因为没有demo,大家不太好理解。所以,大家可以在后期demo上传后再次详细了解)</strong>

    这里我们举个tableView中Cell显示(整个tableView的demo将会在View篇结束后放出),其中Model要做的事情,我们先看在Model中的代码:

    - (void)setupInfo {
        self.xibName = @"SaleInfoTableViewCell";/指定Cell的Cell样式
        self.cellHeight = @(76);//指定Cell的高度
        self.ideltifier = @"cell";//指定Cell的ideltifier
    }
    

    通过在Model生成时执行这个方法,实现了Cell样式与样式数据分离,做到了每个Cell的View样式与Model的绑定。

    <strong>在讲解Model的结尾,总结起来就是:Model存在的目的是为了给业务工程师一个友好稳定的数据,让业务工程师在相应的模块内独立地做相应的数据操作。</strong>

    <strong><h4>二、ViewModel--做一个优秀的数据工厂</h4></strong>
    如文章开始的图就知道,ViewModel更像一个食品工厂一样,将不同的原料通过不同的制作工艺产出为统一的产品。
    那么作为工厂的框架BaseViewModel应该是怎样的呢?
    首先我们应该先想到,我们的原料(数据)来自哪里?
    基本都是来自网络了噻!
    既然来自网络,那么就明确了BaseViewModel应该实现三个事情:网络通信、上传、下载。(都用af第三方库实现)
    废话不多说,直接上代码:

    <strong>1、BaseViewModel实现的三个方法</strong>

    <strong>网络请求:</strong>

    - (void)serviceNetWorkWithUrlStr:(NSString *)urlStr//请求网络地址
                              Params:(NSMutableDictionary *)params//请求参数
                             Success:(void(^)(id result))successBlock
                             Failure:(void(^)(NSError *error))failBlock {
        AFHTTPRequestOperationManager * manager = [AFHTTPRequestOperationManager manager];
        manager.requestSerializer.timeoutInterval = 10;//请求时长
        manager.requestSerializer = [AFHTTPRequestSerializer serializer];
        manager.responseSerializer = [AFHTTPResponseSerializer serializer];
    //如果需要加解密,可引入加解密的工具类将params加密实现
        [manager POST:urlStr parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
            successBlock(responseObject);
            
        } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
            NSLog(@"%@", [error localizedDescription]);
            failBlock(error);
        }];
        
    }
    

    <strong>上传:</strong>
    该上传方法需要求能同时多传,且传不同类型的文件,以适应不用的场景需要

    - (void)uploadFileWithfileList:(NSMutableArray *)params//存放上传文件的数组
                          Option:(NSDictionary *)optiondic//请求参数
                           Url:(NSString *)requestURL//上传网络地址
                             Success:(void(^)(id responseObject))successBlock
                             Failure:(void(^)(NSError *error))failBlock
                             progress:(void (^)(float progress))progress{
        AFHTTPRequestOperationManager *manager=[AFHTTPRequestOperationManager manager];
        //设置返回的数据解析格式
        manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:@"text/html"];
        manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:@"application/json"];
        AFHTTPRequestOperation *operation = [manager POST:requestURL parameters:optiondic constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
           //下方举了一个极端例子:当数组里存放了不同类型的文件如何上传
            for (int i = 0; i<params.count; i++) {
                if ([params[i] isKindOfClass:[UIImage class]]) {
                    
                    UIImage *image = [params[i] imageCompressForWidth:params[i] targetWidth:375];
                    NSData *imageData =UIImagePNGRepresentation(image);
                    [formData appendPartWithFileData:imageData name:@"file_content" fileName:[NSString stringWithFormat:@"anyImage_%d.png",i] mimeType:@"image/png"];
                }else if ([params[i] isKindOfClass:[NSString class]]) {
                    NSURL *url = [NSURL fileURLWithPath:params[i]];
                    NSData *data = [NSData dataWithContentsOfURL:url];
                    [formData appendPartWithFileData:data name:@"file_content" fileName:@"11.aac" mimeType:@"audio/x-mei-aac"];
                    
                }else if ([params[i] isKindOfClass:[NSURL class]]) {
                    NSData *data = [NSData dataWithContentsOfURL:params[i]];
                    [formData appendPartWithFileData:data name:@"file_content" fileName:@"11.aac" mimeType:@"audio/x-mei-aac"];
                }
                
            }
        } success:^(AFHTTPRequestOperation *operation, id responseObject) {
    
            successBlock(responseObject);
            
        } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
            failBlock(error);
        }];
        //获得上传进度
        [operation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
            NSLog(@"百分比:%f",totalBytesWritten*1.0/totalBytesExpectedToWrite);
            progress(totalBytesWritten*1.0/totalBytesExpectedToWrite);
            
            
        }];
    }
    

    <strong>下载:</strong>

    - (void)downloadFileWithOption:(NSDictionary *)paramDic//请求参数
                     withUrl:(NSString*)requestURL//下载地址
                         savedPath:(NSString*)savedPath//保存路径
                   downloadSuccess:(void (^)(id responseObject))success
                   downloadFailure:(void (^)(NSError *error))failure
                          progress:(void (^)(float progress))progress
    
    {
        AFHTTPRequestSerializer *serializer = [AFHTTPRequestSerializer serializer];
        NSMutableURLRequest *request =[serializer requestWithMethod:@"POST" URLString:requestURL parameters:paramDic error:nil];
        AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc]initWithRequest:request];
        [operation setOutputStream:[NSOutputStream outputStreamToFileAtPath:savedPath append:NO]];
        [operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
            float p = (float)totalBytesRead / totalBytesExpectedToRead;
            progress(p);//下载进度
            NSLog(@"download:%f", (float)totalBytesRead / totalBytesExpectedToRead);
            
        }];
        
        [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
            success(responseObject);
            NSLog(@"下载成功");
            
        } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
            failure(error);
            
            NSLog(@"下载失败");
            
        }];
        
        [operation start];
        
        
    }
    

    <strong>2.如何写好一个子ViewModel</strong>

    不知道大家注意到没有,其实我们真实的项目中,网络请求返回来的状态其实可能是会存在三种状态的:<strong>请求成功;请求失败,服务器返回错误信息;请求失败,网络不通。</strong>同时,<strong>我们会在某些地方做缓存读写</strong>。那既然如此,继承BaseViewModel的子ViewModel的对外接口(<strong>唯一对外接口</strong>)应是如下所写:

    - (void)serviceNetWorkMessageListWith
                                 Success:(void(^)(id responseObject))successBlock//请求成功回调
                             ReceiveFail:(void (^)(id responseObject))ReceiveFailBolck//请求失败,服务器返回错误信息
                                 Failure:(void(^)(NSError *error))failBlock//请求失败,网络不通 {
        NSMutableDictionary *tempDic = [@{@"service_code":Message_Service_Code} mutableCopy];
        [tempDic  addEntriesFromDictionary:[self getPrivateParameters]]//所有接口的公共访问参数;
        [tempDic addEntriesFromDictionary:@{"":""}//对应接口的对应参数];
    //这里举个Archive做数据缓存的读取与存储的例子,方便大家理解在某些场景下需要某个ViewModel作缓存时,如何只对外提供一个数据接口,里面作多级缓存的原理,具体实现大家可以结合自身优化,比如数据库的缓存,比如在这个类单独归一个方法。
    if ([[NSUserDefaults standardUserDefaults] objectForKey:Message_Service_Code]) {
                    NSArray *priarr = [[NSUserDefaults standardUserDefaults] objectForKey:Message_Service_Code];
                    NSMutableArray *priMuarr = [NSMutableArray arrayWithCapacity:0];
                    for (NSData *data in priarr) {
                        
                        MessageInfoModel *message =  [NSKeyedUnarchiver unarchiveObjectWithData:data];//解档
                        [priMuarr addObject:message];
                    }
                    successBlock(priMuarr);//返回缓存数据
                }
        [self serviceNetWorkWithUrlStr:[UrlGenerator queryNewMessageUrl] Params:tempDic Success:^(id responseObject) {
    
     if ([responseObject[@"code"] isEqualToString:@"0000"]) {
                
                
                NSArray *array = [MessageInfoModel mj_objectArrayWithKeyValuesArray:responseObject[@"data"]];
                NSMutableArray *enArr = [NSMutableArray arrayWithCapacity:0];
                for (MessageInfoModel *messageModel in array) {
                    
                    
                    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:messageModel];
                    [enArr addObject:data];//归档
                    
                    
                }
                [[NSUserDefaults standardUserDefaults] setObject:enArr forKey:Message_Service_Code];
                successBlock(array);//返回网络请求数据
                
                
            }else {
                
                ReceiveFailBolck(responseObject[@"msg"]);
            }
            
            
        } Failure:^(NSError *error) {
            failBlock(error);
        }];
    }
    

    <strong>3、到底ViewModel放在哪里合适?创建多少个ViewModel合适?</strong>

    开门见山直说,个人认为只要涉及数据的View的模块,最理想的情况应该一个View模块一个ViewModel。
    因为,只有这样,在后期项目越来越庞大,维护的人员越来越多的时候,才能保证模块之间的绝对独立。
    (如果是一两个人开发的中小型项目,也没必要一个模块对一个ViewModel,本身功能不多,没有必要。这时可以一个Controller对一个ViewModel,或者几个同需求功能的Controller对一个ViewModel。项目死的,人是灵活的,具体情况具体分析。)

    我举两个例子:

    a.某个模块因为维护人员频次多,需求改动频繁,到最后需要大改某个模块的时候,因为之前的至上到下(数据到界面)的绝对独立,可以完全抽出来,重新制作一个新的模块,重新替换进去;
    b.某些模块需要数据缓存,某些模块又需要网络状态判断(含大图片展示的模块),完全可以针对不同情况针对这些相应模块对应的ViewModel数据工厂做相应的事情。
    以上两个例子,我想做过项目的,尤其是遇到频繁更改需求的,应该是深有体会(哎,我本人就特别有体会/(ㄒoㄒ)/~~)。


    借用一位大神的话结束Model篇:

    <em><strong>“你必须得清楚你要做什么,业务方希望要什么。而不是为了架构而架构,也不是为了体验新技术而改架构方案。以前是MVC,最近流行MVVM,如果过去的MVC是个好架构,没什么特别大的缺陷,就不要推倒然后搞成MVVM。”</em></strong>

    后面几天我在各位大神的建议下会不断优化文章各个细节,欢迎多多关注,共同学习。几天后会发出<strong> view--业务与界面结合的模块开发 </strong>请多关注。

    相关文章

      网友评论

      • 其实一直都很好:楼主是不是最近很忙啊 一直没有更新啊 :stuck_out_tongue_winking_eye:
      • 其实一直都很好:楼主 什么时候 把demo放出来 一直在等待着
      • 其实一直都很好:非常期待楼主把demo放出来,最近因为公司需要,正在学习MVVM开发模式,受益很大,再次感谢楼主分享,其实个人觉得可以将网络请求封装单独搞一下,使用单例类,在使用的使用直接用就好了 没必要 在BaseViewModel中封装网络请求
        数聚宝土豪君:因为viewmodel不仅仅是请求哦,比如网络请求成功后要做缓存。下次直接通过这个viewmodel直接获取数据就通过缓存了。不同的viewmodel对不同view输出相应数据。所以,一个单例难以做到一个个性化的数据工厂的
      • SuyChen:一个页面多个请求的时候才用viewmodel吗?是不是都是通过block回调?之前不是说viewmodel是放代码逻辑的吗?如果把C里面的代码逻辑放到Viewmodel里面该怎么绑定?都用block回调吗?
        数聚宝土豪君:@jennyChen demo马上就出来了,到时可一起交流
        SuyChen:@立志程序猿的小青年 你的观念就是数据请求在viewmodel里,代码逻辑在view里,然后一些传递在Controller里是吧?我们公司之前用的代码就是用rac写的,view的代码逻辑都是在viewmodel里实现的,比如说一些点击跳转什么的就在VM里面实现的,然后在C里面绑定的数据,这种代码我还是看不太懂,但是他MVVM的思想不错,我想用那种思想,但是如果不用rac就不知道怎么在C里面如何绑定VM里面的代码
        数聚宝土豪君:@jennyChen 1.是一个view模块对应一个viewModel哦,比如这个view模块有三个数据访问,那么对应的viewModel就用三个相应的数据请求的方法哦;2.viewModel中每个数据请求都有三个block,分别是数据获取成功/服务器请求错误/请求失败。3.业务逻辑代码是与view模块相关的,肯定是在view模块里面,数据处理的代码肯定是在viewModel,C只是做了将viewmodel和view模块连接在一起和view模块之间的通信而已。4.大部分用的是block回调,有点类似rac响应式编程。具体实例demo以及view模块的文章将会马上给出,请多多关注。 :smiley:
      • 烂裤衩儿:默默点赞君
        烂裤衩儿:@烂裤衩儿 期待 DEMO
      • 奔跑的小火车:首先感谢楼主不辞辛苦的写文章。
        其次说下自己观点:
        ViewModel相当于是一个管理类。
        以前MVC的时候,数据的请求一般都是在Controller里面完成,MVVM里面只不过是放在了一个专门管理的地方,这样降低了代码的耦合性,以后维护起来也方便。就个人而言,看情况吧,如果控制器里面涉及很多复杂网络请求等逻辑,建议单独搞一个ViewModel,如果仅仅是一个请求接口和展示更多接口,那么建立一个ViewModle就有点大材小用了,视情况而定吧,哈哈
        数聚宝土豪君:@奔跑的小火车 你说的这个情况我考虑过,所以我文章末尾强调了这个事情。因为对于产品需求的迭代,开发人员的流动和多人开发的协调来说,一个模块一个viewmodel,能保证功能模块从数据到界面的完全与其他模块独立,这样才能业务工程师在指定范围内做指定的事情,无论做数据缓存还是其他数据操作,都不会影响到其他模块。即时这个模块后期被维护得乱得不像样了,仍然可以将整个模块替换。比起初期建立较多的类来说,保证了后期的迭代和多人协调开发。个人觉得利大于弊。
      • AnAn_AnAn:额 ---- 楼主 什么时候 放个 Demo
      • 瘦不下来的悟空:终于看到结合例子说MVVM的了!
        数聚宝土豪君:@瘦不下来的悟空 谢谢,多交流:yum:
      • liang1991:"字典会因为没有取值的这个key(因输入错误等原因),导致程序崩溃,而Model不会;" 字典如果key不存在应该是返回nil吧,不会引起崩溃。
        liang1991:@立志程序猿的小青年 不客气 哈:blush: ,文章写的很好 :+1:
        数聚宝土豪君:@liang1991 非常感谢指出我表达上的错误:yum:
        数聚宝土豪君:@liang1991 不好意思,这段话,我没有表达清楚,应该是这样的:"字典会因为没有取值的这个key(因输入错误等原因)或者是取到其他类型的值,取出来的nil或者其他类型的值,而赋值给label之类的文字控件,会导致程序崩溃,而Model不会;"
      • 心中的信念:非常期待接下来的文章
      • 心中的信念:确实不错,很务实,牛逼
        心中的信念:@立志程序猿的小青年 赶紧出下面的文章等不急呀:smile:
        数聚宝土豪君:@心中的信念 谢谢:yum:多多关注啦
      • 95c9800fdf47:你的那个快速打印model属性的方法老是报错!
        数聚宝土豪君:@博爱1616 这周末就上一个整体的demo,麻烦多关注了:yum:
        95c9800fdf47:@立志程序猿的小青年 我集成nsobject类,然后获取的数据是字典,但是用这个方法就不行!我给删了,你上个demo,我看看你怎么用的!
        数聚宝土豪君:@博爱1616 能麻烦告知下是报的什么错误吗?这个方法我直接拿的我稳定工程里的,应该不会报错的
      • jhbook:和直接把网络请求写在Model里有什么区别?
        数聚宝土豪君:@JiaHLiang 不仅是这样的,你更应该把Model就当成数据,在Model层只写一些在读取与存储统一操作的方法(目的是为了给业务工程师统一友好的数据供操作),再在另外一个地方做具体的一些业务操作,这是为了在需求变动时,我们只需要变成ViewModel的一些方法。就好比食品加工厂,将不同的原料加工成不同的食物产品,肯定不可能在食物产品里还能再加工其他东西噻。
        jhbook:@立志程序猿的小青年 也就是说要避免Model里引入其他的Model吗?
        数聚宝土豪君:@JiaHLiang 区别很大哦。首先你先形象理解ViewModel为一个数据加工厂,把外部的数据加工成 Model。然后你问的重点来了,我们也提到了,ViewModel对应一个业务模块,那么有些业务模块需要的可能是几种Model,如果把网络请求写到一个Model里,如果涉及到几个Model之间的数据加工怎么办呢?所以,我们应该把数据本身和数据加工分开,提高独立性。
      • 293524a39c08:可以可以可以
      • glhmtech:牛逼

      本文标题:实际项目中的MVVM(积木)模式第二章:model--如何高效利

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