实践MVVM设计模式

mvc模式的缺点。
1.c层(viewController层),代码冗余,不方便阅读。一般包括以下几点:
1)界面构建。
2)网络数据的请求和后续处理。
3)响应逻辑。比如对点击事件的处理、很多delegate方法。
4)数据源方法。比如常用的tableView会有datasource方法,其中数cellForRowAtIndexpath最为典型,其中数据展示前的代码也是非常的多。
举个例子:在使用tableView时,section、row的判断很头疼,点击时间的if 、switch判断更加让人烦躁。如:

c层的逻辑处理.png

不加注释的行为简直受不了!
其次,代码不够灵活,后期难以维护。想要增加、删除的功能时,不仅要修改数据源(self.storeArray)中的数据,还要修改tableView中返回的section,row的数量,还需要修改cellForRow中,view的赋值,和对cell点击事件的处理,感觉无从下手。
最后,c层代码太多,仅仅一个界面就达到了1,2千行左右的代码,寻找很不方便。(尤其是不加注释!)
那么接下来,我们主要介绍下传说的MVVM模式,即Model +View +ViewModel。其实相比于MVC,重点在于ViewModel,我的理解就是,将我们对传统的Model进行继承和重用。下面通过一个例子来说说。


界面.png

首先,我们可以将界面根据cell类型,去分几大类。如,该界面中,第一类"地址"cell,第二类"药店"cell,第三类''其他''cell(都的左侧、右侧都是文本型)。
这样我们就可以就只需要3个model,来分别储存在3中cell的数据。但是这3中model也有很多通用的数据,如cell的种类,cell的高度,cell是否要显示,cell是否能点击等等。这就需要一个基类,来存放这些通用的属性。上述的3个model来继承这个基类,来实现model的重用。
如:我们通过建立SubmitOrderBaseModel为基类

@interfaceSubmitOrderBaseModel :NSObject
/**
cell高度
*/
@property(nonatomic,assign)CGFloatcellHeight;
/**
cell类型
*/
@property(nonatomic,copy)NSString* type;
/**
model名称
*/
@property(nonatomic,copy)NSString* title;
/**
是否显示
*/
@property(nonatomic,assign)BOOLisShow;
/**
是否能点击
*/
@property(nonatomic,assign)BOOLisSelected;
/**
子model
*/
@property(nonatomic,strong)NSMutableArray*models;
@end

一定要加注释!
"地址"cell,SubmitOrderAddressModel,继承SubmitOrderBaseModel

@interface SubmitOrderAddressModel : SubmitOrderBaseModel

@end

"药店"cell,SubmitOrderShopModel,继承SubmitOrderBaseModel

@interface SubmitOrderShopModel : SubmitOrderBaseModel

@end

其他cell,SubMitOrderNormalModel,继承SubmitOrderBaseModel

@interface SubMitOrderNormalModel :SubmitOrderBaseModel

/**
 右侧文字输入方向
 */
@property(nonatomic,strong)NSString *rightTextAlignment;

/**
 右侧字体颜色
 */
@property(nonatomic,strong)UIColor *rightColor;
@end

''其他''cell中又有写微小的区别,比如右侧文本的颜色和输入方向,这是''其他''cell中的独有属性。
好,现在我们可以在ViewController中,根据界面的需求来处理数据。在上面的''界面''中,我们分析了需要3种model来储存数据,但是他根据业务逻辑分了6个(其实是大于6个,一个药店一个section)section。这样的话,可以根据section的个数来处理数据。创建个一个大数组(orderTypeArray),其中储存了我们所要展示的所有数据,他的个数就是section的个数。
在处理数据时,我遇到的问题是,“其他”cell,type都是一种属性,你要在models属性中添加要展示的cell数据,封装成一种数据格式,比如我封装成字典以"NAME"为key,方便赋值时使用。
整理后的数组大概是这个样子的:


处理后的数据.png

现在拿到数组后,就可以去赋值了。首先我们把tableView的delegate拿出来,独立成一个类,然后在tableView设置代理和数据源时,绑定在个类。

    _delegate = [[SubmitOrderTableViewDelegate alloc] init]; 
    _mainTableView.delegate =_delegate;
    _mainTableView.dataSource =_delegate;

要记得在SubmitOrderTableViewDelegate.h中遵守UITableViewDataSource,UITableViewDelegate。
然后我们就可以在SubmitOrderTableViewDelegate.m中实现要展示的数据了。
1) 在numberOfSectionsInTableView方法中,返回当然是数组(orderTypeArray)中model的个数了。

    return _orderTypeArray.count;

2)在numberOfRowsInSection方法中,返回的是每个model中models中的对象个数。

    SubmitOrderBaseModel *model =_orderTypeArray[section];
    
    if ([model.type isEqualToString:@"OrderAddressTableViewCell"] || [model.type isEqualToString:@"GoodsShopTableViewCell"] )//地址、药品只返回1
    {
        return 1;
    }
    return model.models.count;

3)在heightForRowAtIndexPath方法中,返回的是我们model.cellHeight参数。

    SubmitOrderBaseModel *model =_orderTypeArray[indexPath.section];
    
    CGFloat height =0;
    
    if (!model.cellHeight)
    {
        SubmitOrderBaseModel *subModel =   model.models[indexPath.row];
        
        if (subModel.isShow)                //是否显示
        {
            height = subModel.cellHeight;
        }
        else
        {
            height = 0;
        }
    }
    else
    {
        height = model.cellHeight;          //地址、药品cell
    }
    return height;

这里需要注意的是"是否显示cell"这个属性。
现在来到关键的cellForRowAtIndexPath方法。

    NSString *cellType =@"";
    SubmitOrderBaseModel *data = _orderTypeArray[indexPath.section];
    SubmitOrderBaseModel *subData = data.models[indexPath.row];
    if (!data.type) //地址、药品Type
    {
        cellType = subData.type;
    }
    else
    {
        cellType = data.type;
    }
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellType];
    if (cell ==nil)
    {
        cell = [[NSClassFromString(cellType) alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellType];
        cell.selectionStyle = UITableViewCellSelectionStyleNone;
    }

首先根据cellType来创建cell。然后根据cell的类型(也就是cellType的类型)来为指定的cell赋值。
比如,当前cell是''地址''cell

     if ([cell isKindOfClass:[OrderAddressTableViewCell class]])
     {
       ((OrderAddressTableViewCell *)cell).accessoryType = UITableViewCellAccessoryDisclosureIndicator;
        
        ((OrderAddressTableViewCell *)cell).nameLabel.text = ((NSDictionary*)subData)[@"RECEIVER"];
        ((OrderAddressTableViewCell *)cell).phoneLabel.text = ((NSDictionary*)subData)[@"CONTACTPHONE"];
        ((OrderAddressTableViewCell *)cell).addressLabel.text = ((NSDictionary*)subData)[@"ADDRESS"];
    }

这些都是简单的为控件赋值。
在比如,“其他”cell

     if ([cell isKindOfClass:[ActionCell class]])
      {
        if (((SubMitOrderNormalModel*)subData).isShow)
        {
            ((ActionCell *)cell).hidden = NO;
        }
        else
        {
            ((ActionCell *)cell).hidden = YES;
        }
        if (((SubMitOrderNormalModel*)subData).isSelected)
        {
            ((ActionCell *)cell).accessoryType = UITableViewCellAccessoryDisclosureIndicator;
        }
        else
        {
            ((ActionCell *)cell).accessoryType = UITableViewCellAccessoryNone;
        }
        
        ((ActionCell *)cell).titleLab.text = ((SubMitOrderNormalModel*)subData).title;          //标题
        
        NSDictionary *infoDic =((SubMitOrderNormalModel*)subData).models[0];                    //详情信息
        ((ActionCell *)cell).actionLab.text = infoDic[@"NAME"];
        
        if (((SubMitOrderNormalModel*)subData).rightColor)                                      //详情字体颜色
        {
            ((ActionCell *)cell).actionLab.textColor= ((SubMitOrderNormalModel*)subData).rightColor;
        }
        else
        {
            ((ActionCell *)cell).actionLab.textColor= [UIColor blackColor];
        }
        
        if ([((SubMitOrderNormalModel*)subData).rightTextAlignment isEqualToString:@"left"])    //详情信息输入方向
        {
            ((ActionCell *)cell).actionLab.textAlignment = NSTextAlignmentLeft;
        }
        else
        {
            ((ActionCell *)cell).actionLab.textAlignment = NSTextAlignmentRight;
        }

到这里可以看出,我们并没有通过 section和row的顺序来为控件赋值,而是通过我们传入的数据来映射到特定的cell。
然后是点击事件,这里我们需要把点击的indexPath,和model传出去进行处理就可以了。

到这里,大家可能会对MVVM有了一定的认识,这种通过只对数据源处理,就可以展示我们界面的内容。极大的增加了代码的灵活性,在以后维护时,我们要想改变cell的展示顺序,或者新增、删除某个cell,我们都可以通过对数据源(即orderTypeArray)的存储顺序,增加、删除某条数据就可以了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容