美文网首页IOS收藏iOS之设计模式IOS
我对iOS开发中使用MVVM的理解和使用(初级)

我对iOS开发中使用MVVM的理解和使用(初级)

作者: 挂着铃铛的兔 | 来源:发表于2016-11-16 16:04 被阅读8077次

    前言 MVVMDemo

    之前几个月一直在学习react-native,它的组件化开发真的是很棒,控件和页面的组件化在开发中可以很好的复用,节省开发时间。在那个时候还不知道react-native开发用到的就是MVVM设计模式。
    前几天,UI给了新的需求,需要添加几个页面(之前的项目一直使用MVC开发的),在给这几个新页面添加入口的时候,感觉之前写的代码真的是好恶心😱😱😱,就在网上搜了搜MVP和MVVM,发现MVVM和我在写RN时的写法很像。就研究了一下,然后写下了这篇文章。(可能会有很多问题,欢迎评论)
    ps:这篇文章实用为主,那些理论性的东西,我都没有研究。
    俗话说得好:黑猫白猫,能用在项目中的就是好🐱

    更新

    解决了,之前存在的button需要在HeaderView中复写的问题。

    吐槽

    之前在网上搜索MVVM设计模式的时候,很多文章都说到MVVM和ReactiveCocoa最好是搭配在一起使用,这样效率更高。我承认ReactiveCocoa是个好东西,但我为什么没有研究,而只写了这个简单的MVVMDemo呢?因为我觉得,虽然直接使用MVVM会有数据绑定方面的问题,但如果为了使用MVVM还要在项目中集成ReactiveCocoa,对团队开发都不是很友好的,而我本身也只是希望将项目中的一些类拆分,用简单的MVVM就足够了,这样可能省下大量的时间成本去做别的事情。
    当然,以后如果有机会,我也可能会将项目使用MVVM和ReactiveCocoa来重构。

    使用

    MVVM顾名思义,那就是Model,View,ViewModel,所以我们需要创建这些类了。


    项目目录.png

    接下来就把我的理解说说。

    ViewModelClass

    ViewModelClass.png

    ViewModelClass.h

    #import <Foundation/Foundation.h>
    #import <UIKit/UIKit.h>
    
    //定义返回请求数据的block类型
    // 成功返回的数据
    typedef void (^ReturnValueBlock) (id returnValue);
    // 失败返回的数据
    typedef void (^ErrorCodeBlock) (id errorCode);
    
    @interface ViewModelClass : NSObject
    
    @property (strong, nonatomic) ReturnValueBlock returnBlock;
    @property (strong, nonatomic) ErrorCodeBlock errorBlock;
    
    // 传入交互的Block块
    -(void) setBlockWithReturnBlock: (ReturnValueBlock) returnBlock
                     WithErrorBlock: (ErrorCodeBlock) errorBlock;
    @end
    

    ViewModelClass.m

    #import "ViewModelClass.h"
    #import "RTNetworking.h"
    
    @implementation ViewModelClass
    
    #pragma 接收传过来的block
    -(void) setBlockWithReturnBlock: (ReturnValueBlock) returnBlock
                     WithErrorBlock: (ErrorCodeBlock) errorBlock
    {
        _returnBlock = returnBlock;
        _errorBlock = errorBlock;
    }
    @end
    

    上面的两个类放着的就是ViewModel的基类,用下面的方法承接之后继承于这个基类的VM中的回调数据。

    - (void)setBlockWithReturnBlock: (ReturnValueBlock) returnBlock
                     WithErrorBlock: (ErrorCodeBlock) errorBlock;
    

    ViewController

    我之前的项目用的MVC设计模式,C指的就是这个ViewController了,之前写的垃圾代码,一个Controller里面放过1000多行代码,现在去找个方法需要N久。但虽然这是个简单的例子,但真正利用之后并不简单。
    ViewController.m

    // 初始化HeaderVM
    HeaderVM *headerView = [[HeaderVM alloc] init];
    // 初始化HomeVM
    HomeVM *model = [[HomeVM alloc]init];
    // 调用ViewModelClass基类的方法,来获取数据
    [model setBlockWithReturnBlock:^(id returnValue) {
            
            _dataArray = returnValue;
            _listArray = returnValue[@"picList"];
            _categoryArray = returnValue[@"category_new"];
            
            UIView *view = [headerView headerViewWithData:_categoryArray];
            self.tableView.tableHeaderView = view;
    
            [self.tableView reloadData];
    
        } WithErrorBlock:^(id errorCode) {
            
            NSLog(@"%@",errorCode);
            
        }];
    
    

    上面的代码虽短,但最重要的东西都在里面,通过Block回调,将需要的数据在VM页面回传了回来。具体内容见MVVMDemo

    HomeVM

    HomeVM.png

    HomeVM.h

    #import "ViewModelClass.h"
    
    @interface HomeVM : ViewModelClass
    
    // 获取商品列表
    - (void)fetchShopList;
    // 跳转到商品详情页
    - (void)shopListDetailWithVC:(UIViewController *)vc didSelectRowAtDic:(NSDictionary *)dic;
    
    @end
    

    HomeVM.m

    #import "HomeVM.h"
    #import "RTNetworking.h"
    #import "DetailViewController.h"
    
    @implementation HomeVM
    
    - (void)fetchShopList{
        [RTNetworking getWithUrl:@"/v1/Home/all.json" refreshCache:NO success:^(id response) {
            [self loadDataWithSuccessDic:response];
        } fail:^(NSError *error) {
            self.errorBlock(error);   
        }];
    }
    - (void)loadDataWithSuccessDic:(NSDictionary *)dic{
        NSMutableArray *arr = dic[@"data"];
        self.returnBlock(arr);
    }
    - (void)shopListDetailWithVC:(UIViewController *)vc didSelectRowAtDic:(NSDictionary *)dic{
        DetailViewController *view = [[DetailViewController alloc]init];
        view.labelText = dic[@"title"];
        [vc.navigationController pushViewController:view animated:YES];
    }
    @end
    

    可以明显看出HomeVM是继承于ViewModelClass,在这个VM中,将Push到新页面的方法也写在了里面。

    HeaderView

    这个是tableView的headerView。

    HeaderView.png

    HeaderView.h

    #import <Foundation/Foundation.h>
    #import <UIKit/UIKit.h>
    
    typedef void(^HeaderViewBlock)(NSString *shopId);
    
    @interface HeaderView : UIView
    
    @property (nonatomic, copy) HeaderViewBlock block;
    
    // headerView中的数据
    - (void)headerViewWithData:(id)data;
    
    - (void)setBlock:(HeaderViewBlock)block;
    
    @end
    

    HeaderView.m

    #import "HeaderView.h"
    #import "UIKit+AFNetworking.h"
    
    #define SCREEN_WIDTH [UIScreen mainScreen].bounds.size.width
    
    @implementation HeaderView{
        UIImageView *topImage;
        NSMutableArray *dataArray;
        UIButton *button;
    }
    
    - (void)headerViewWithData:(id)data{
        dataArray = data;
        
        CGFloat btnWidth  = SCREEN_WIDTH * 0.17;
        CGFloat btnHeight = SCREEN_WIDTH * 0.17;
        CGFloat margin=(SCREEN_WIDTH-5*btnWidth)/6;
        
        for (int i = 0; i <dataArray.count; i++) {
            
            int row = i % 5;
            int loc = i / 5;
            CGFloat appviewx=margin+(margin+btnWidth)*row;
            CGFloat appviewy=5 + (10+btnHeight)*loc;
            
            button = [UIButton buttonWithType:(UIButtonTypeCustom)];
            button.frame = CGRectMake(appviewx, appviewy, btnWidth, btnHeight);
            button.highlighted = NO;
            button.tag = [dataArray[i][@"id"] integerValue];
            
            [button setImageForState:(UIControlStateNormal) withURL:[NSURL URLWithString:dataArray[i][@"icon"]]];
            button.userInteractionEnabled = YES;
            [button addTarget:self action:@selector(button:) forControlEvents:UIControlEventTouchUpInside];
    
            [self addSubview:button];
        }
    }
    
    - (void)button:(UIButton *)btn{
        NSString *shopId = [NSString stringWithFormat:@"%ld",(long)btn.tag];
        if (self.block) {
            self.block(shopId);
        }
    }
    @end
    

    我将HeaderView的布局写在了这个View里面,还有HeaderView上的按钮的点击事件,通过block回调,在主页面进行页面跳转操作。

    总结

    可能你会发现这个目录中没有Model,这是因为我做的这个Demo中用Model太浪费,以后,如果我感觉我对MVVM的理解更深一层的时候,会再写一篇关于MVVM的文章,敬请期待啦!
    这个Demo中的数据用的是我公司首页的接口,请不要乱用哦!
    Demo中用到的网络请求是我再封装的一层,用起来还不错,如果有什么好的建议欢迎提出。

    相关文章

      网友评论

      • 小包包包:楼主,编译不过呀
      • 不辣先生:数据绑定呢?怎么没看出双向绑定的思想
        _曾梦想仗剑走天涯:对的 压根就不算是MVVM 建议再去好好了解一下
      • LoveY34:感觉你这个更偏向于MVP啊?而不是MVVM啊?
      • 意_f66a:DetailViewController *detail = [[DetailViewController alloc] init];
        这句放到block 中,
        否则DetailViewController 不能释放, 也不能赋值
      • zzq_hd:加上RAC订阅信号处理会更舒服
      • 小菜鸟爱开发:感觉就是MVP的模式,没真正写出MVVM的感觉啊
        挂着铃铛的兔:@小菜鸟爱开发 是的。。。:cry:
      • 遛遛食:请问VM中可以放几个网络请求接口啊?
        我在VM中放了两个请求,请求回来的数据都调用block,会导致一个VM类两个请求后执行一个block,这个怎么解决啊?
        是一个网络请求就要写一个VM类吗?
        挂着铃铛的兔:我的理解是在VM里面处理数据,那将两个请求都放在VM里面,只要将最终数据返回。 推荐使用YTKNetwork哦
      • Maj_sunshine:我代码能看懂 就是不理解为什么mvvm中哪部分是处理用户交互的 网络请求算是放在mv 中的么。
        挂着铃铛的兔:@学污直径 如果真想用mvvm,推荐学习RactiveCocoa来配合,如果用我这种思想,本身没错但用起来会有很大的弊病
        Maj_sunshine:@学污直径 那就是事件处理页面跳转和网络请求都是写在vm中的么 通过block返回值咯 那什么tableview 高度计算 也要写进去么 才实习几个月的菜鸟急需请教:confounded:
        挂着铃铛的兔:之前的理解是将网络请求与处理都放在vm中,c中只需要使用数据。
      • 魈畈:不错( ^_^ )
        挂着铃铛的兔:@魈畈 谢谢
      • userName:// 初始化HeaderVM
        HeaderVM *headerView = [[HeaderVM alloc] init];
        这句话 是不是 初始化的是一个View?
        挂着铃铛的兔:@userName 不是的。只是初始化vm
      • userName:@property (strong, nonatomic) ErrorCodeBlock errorBlock;
        应该写成
        @property (copy, nonatomic) ErrorCodeBlock errorBlock;
        会更好吧.
        挂着铃铛的兔:@userName 好的,谢谢。在写这篇文章的时候,还是有很多漏洞的
      • 7043a71ff0c3:你是我第一个关注RN的朋友,写得不错。有自己的理解,我博客还没写东西,不过以后会花时间写的。希望大家可以一起探讨
        挂着铃铛的兔:一起成长:+1::+1:
      • 司马捷:可以
        挂着铃铛的兔:@机器人小雪 谢谢
      • 雪_晟:缺少工程那个文件
        雪_晟:@挂着铃铛的兔 写的不错:sparkling_heart:
        雪_晟:@挂着铃铛的兔 你下载下你的demo 就知道啦
        挂着铃铛的兔:@miss李manman 缺少哪个文件?

      本文标题:我对iOS开发中使用MVVM的理解和使用(初级)

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