美文网首页首页投稿(暂停使用,暂停投稿)iOS DeveloperiOS程序猿
MVVM模式下如何使用ReactiveCocoa响应链式编程&l

MVVM模式下如何使用ReactiveCocoa响应链式编程&l

作者: 写代码的小农民 | 来源:发表于2017-02-08 16:07 被阅读306次

    前一阵子公司要求项目从新架构,但又只给不到一个月的时间,这显然是不可能的。但从新架构又是在所难免的,和同事商定后决定一部分交互逻辑比较少的界面先使用MVVM架构,然后慢慢修改。下面整理了一下这次重构的遇到的问题,并希望能给大家一些帮助。
    1.ReactiveCocoa的使用
    要使用MVVM模式编程收下选择一个框架,当然不仅仅是ReactiveCocoa这一个框架,这里就不多说。当然我也没用过别的,如果哪位看官用过可以多多指教。接下来我就按步骤说了:
    第一步:导入ReactiveCocoa框架,建议使用Cocopods管理这个第三方SDK,因为他帮我们解决了不少麻烦。首先你不用自己封装静态库,其次你也不用担心库路径会找不到,最后由于这个sdk目前使用的人并不多,所以以后随着使用者越来越多肯定会有更新。
    第一步:导入ReactiveCocoa框架,建议使用Cocopods管理这个第三方SDK,因为他帮我们解决了不少麻烦。首先你不用自己封装静态库,其次你也不用担心库路径会找不到,最后由于这个sdk目前使用的人并不多,所以以后随着使用者越来越多肯定会有更新。



    关于sdk的下载可以参考截图,不多做介绍。其中MJExtension这个sdk也顺便导一下,因为MVVM少不了字典转模型的。当然不用也可以!

    第二部:先从一个简单的界面开始,这是第一个MVVM模式下运用Ractivecocoa编写的第一个界面,不过基本涵盖了RAC(一下Ractivecocoa简称RAC)复杂Model下的数据请求和传递,先看一下界面实现的效果。点击第一个界面的历史红包,跳转到发红包界面:



    我先把总体的方法给粘贴出来,首先有一个总体的思路把控,首先实现的的是历史红包点击cell的代理方法:

    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
        UIStoryboard *storyBoard = [UIStoryboard storyboardWithName:@"TableView" bundle:nil];
        //1.MVVM下的MVVMCheakLuckViewController
        self.checkRedBag= (MVVMCheakLuckViewController *)[storyBoard instantiateViewControllerWithIdentifier:@"MVVMCheakLuckViewController"];
        NSDictionary *detailDic =self.dataArr[indexPath.row];
        NSString *bonus_codeStr = [NSString stringWithFormat:@"%@",[detailDic objectForKey:@"bonus_code"]];
        //2.抽象出一个“下载类”(可以把url或者需要上传的数据抽象成NetWorkingModel的属性)
        NetWorkingModel * networkModel = [[NetWorkingModel alloc] init];
        networkModel .bonusCode = bonus_codeStr;
        networkModel.urlPath = CheakLuck_URL;
        //3.初始化MVVMCheackLuckViewModel,这个就是MVVM中的ViewModel。正是把以前ViewController里面的数据下载剥离了出来,为ViewController瘦身。初始方法把“下载类”作为参数传入。
        MVVMCheackLuckViewModel * listVM = [[MVVMCheackLuckViewModel alloc] initWithAFRequestParameter: networkModel];
        //4.通过上面的2和3两步,最终的目的是对MVVMCheakLuckViewController的viewModel赋值
        self.checkRedBag.viewModel = listVM;
        //5.万事具备,跳转界面。
        [self.navigationController pushViewController:self.checkRedBag animated:YES];
    }
    

    可能看官对MVVM有了一个初步的了解,为了加深看官的理解彻底改变你MVC的编程思维,我把代码与MVC下的界面跳转作对比,实现效果是一样的还是上面的两个界面。

    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
        UIStoryboard *storyBoard = [UIStoryboard storyboardWithName:@"TableView" bundle:nil];
        CheakLuckViewController * checkRedBag= (CheakLuckViewController *)[storyBoard instantiateViewControllerWithIdentifier:@"CheakLuckViewController"];
        NSDictionary *detailDic =self.dataArr[indexPath.row];
        NSString *bonus_codeStr = [NSString stringWithFormat:@"%@",[detailDic objectForKey:@"bonus_code"]];
        checkRedBag.bonusCode = bonus_codeStr;
        [self.navigationController pushViewController:checkRedBag animated:YES];
    }
    

    MVC我就不多做介绍了,相信每个人都很熟悉,看似简单整洁,但正是因为这里的简单整洁,反而加重了ViewController的负担。对比着其实我把沉重的ViewController里面的代码。剥离到2,3和4这三个步骤中去了,我在这仔细论述这三部的实现。
    第2步,这一步很简单,但必须要培养一个建立Model的观念,这个类的代码也很简单:

    #import <Foundation/Foundation.h>
    @interface NetWorkingModel : NSObject
    /**
     下载需要的上传的参数或者要拼接的参数
     */
    @property (strong,nonatomic) NSString *bonusCode;
    @property (strong,nonatomic) NSString *urlPath;
    @end
    

    第3步,这一步是最关键的一步,我们通过上面的下载类在MVVMCheackLuckViewModel中下载数据。这里简单用到了一些RAC的语法,我会做简单的介绍,首先RAC是一个响应式链式编程,我可以理解RAC像一个管道,通过一个个信号连接而成的一个管道。我们先看MVVMCheackLuckViewModel中的代码:
    .h中的代码如下:

    //传的参数
    @property (copy,nonatomic)NSString *bonusCode;
    @property (copy,nonatomic)NSString *requestPath;
    //这个Model中是在关键的Model,存储了下载的数据。而且我们可以在MVVMCheakLuckViewController中通过ViewModel.cashModel的方法的到下载的数据,所以所有剥离出来的过程都是在给cashModel赋值。
    @property (strong,nonatomic) CashRedBagModel *cashModel;
    /对传过来的数据做一下储存
    @property (strong,nonatomic) NetWorkingModel *saveNetModel;
    类的初始化方法,通过这个方法是”下载类“,和ViewModel建立了连接,这才是下载数据成为可能
    - (instancetype)initWithAFRequestParameter:(NetWorkingModel *)NetModel;
    @end
    

    .m中的方法

    - (instancetype)initWithAFRequestParameter:(NetWorkingModel *)NetModel{
        self = [super init];
        self.saveNetModel = NetModel;
        //注册一个信号,它更像一个观察者,始终观察requestPath的值,一旦发生变化才会return Yes.才会执行subscribeNext下面的方法。所以如果以后你要改变requestPath换个Url从新请求数据,也不需要从新写下载的方法了。RAC内部也是通过KeyPath监听的。
        [[RACObserve(self, requestPath) filter:^BOOL(id value) {
            return value;
        }] subscribeNext:^(NSString * path) {
            //NSURLSessionConfiguration是苹果推荐的下载类,所以这里使用了NSURLSessionConfiguration下载数据(AF3.0),如果觉得不习惯可以使用NSURLconnection。AF3.0.4是两种都支持的
            NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
            AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
            //自己封装的方法创建AFrequest并在这个方法中完成数据上传
            NSMutableURLRequest *request = [self creatAFmangerURLRequest:path];
            NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
                if (error) {
                    DLog(@"========Error: %@", error);
                } else {
                    DLog(@"responseObject----------%@",responseObject);
                    NSDictionary *responsedic = [responseObject objectForKey:@"d"];
                    //通过cashModel初始化Taskuseramodel
                    //字典转模型使用的是MJExtension
                    CashRedBagModel *cashModel = [CashRedBagModel mj_objectWithKeyValues:responseObject];
          //1.对cashModel进行初始化,cashModel里还包含一个数组,数组里面存放的是TakeUserModel,对于这种复杂的Model,会在它的初始化方法里完整的对cashModel一一赋值,进而对TakeUserModel也进行初始化。由于这一步比较复杂我先标记一下。……&……&……&……
                    [cashModel initCashsWithDictionary:responsedic];
                    //2.第一步还要关联另外一个Model我们暂称为小Model,我们等会仔细说这个初始化方法。下面这句代码对controllers的Model赋值,经过第一步,这一步是最关键的一步。完成赋值之后就等于完成了数据下载,就只剩下MVVMCheakLuckViewController中ui的搭建和数据的展示了。
                    self.cashModel = cashModel;
                }
            }];
            [dataTask resume];
        }];
        if (nil != self) {
    //我们在上面注册了信号,但是第一次并不执行subscribeNext里的方法。因为self.requestPath = netModel.urlPath这一句之后filter的方法里才会return Yes。
            [RACObserve(self, saveNetModel)  subscribeNext:^(NetWorkingModel * netModel) {
                self.bonusCode = netModel.bonusCode;
                self.requestPath = netModel.urlPath;
             }];
        }
        return self;
    }
    

    上面我们对viewModel进行了初始化,并在初始化方法中对大Model "CashRedBagModel"进行了初始化。现在已经对viewModel中的 cashModel进行了初始化,其实现在我们已经可以开始写MVVMCheakLuckViewController里面的代码了。但是我们在这之前必须要对一个小Model进行初始化,它将用在MVVMCheakLuckViewController里的UITableView里填充Cell。所以我们接下来看看CashRedBagModel的初始化方法:

    .h里面的代码
    
    @interface CashRedBagModel : NSObject
    
    @property (nonatomic,strong) NSString *bless;
    
    @property (nonatomic,strong) NSString *bonusDesc;
    
    @property (nonatomic,strong) NSString *ownerName;
    //这个数组里存放着每一个Cell上需要填充的TakeUserModel,在viewController里面会通过点语法获取到这个数组self.viewModel.cashModel.take_userModelArr
    @property (nonatomic,strong)NSMutableArray * take_userModelArr;
    - (void)initCashsWithDictionary:(NSDictionary *)dic;
    @end
    

    .m文件中的代码

    - (void)initCashsWithDictionary:(NSDictionary *)dic{
        NSMutableArray *articls = [[NSMutableArray alloc]init];
       //MJExtension字典数组 -> 模型数组
        NSArray *dicArr = [TakeUserModel mj_objectArrayWithKeyValuesArray:[dic objectForKey:@"cashBonusDetailList"]];
        // RAC 风格的数组操作.
        RACSequence * newblogViewModels = [dicArr.rac_sequence
                                           map:^(TakeUserModel * model) {
                                               //通过这个Block会通过初始化CellTakerUserViewModel的方式把Model传过去
                                               CellTakerUserViewModel * vm = [[CellTakerUserViewModel alloc] initWithTakeUserModel:model];
                  //return的CellTakerUserViewModel会存放在newblogViewModels.array中
                                               return vm;
                                           }];
      //1.newblogViewModels.array里面放的是CellTakerUserViewModel
        [articls addObjectsFromArray: newblogViewModels.array];
    
      //2.通过这句代码对.take_userModelArr进行了赋值
        self.take_userModelArr = articls;
    
      //对其他属性进行赋值
        self.ownerName = [dic objectForKey:@"ownerName"];
        self.bless = [dic objectForKey:@"bless"];
        self.bonusDesc= [dic objectForKey:@"bonusDesc"];
    }
    

    UITableView中可以通过[indexPath.row]获取对性的CellTakerUserViewModel进行操作,如下所示:

    CellTakerUserViewModel * vm = self.viewModel.cashModel.take_userModelArr[indexPath.row];
        NSArray * arr = vm.dataArr;
        cell.textLabel.text = [NSString stringWithFormat:@"%@",arr[0]];
    

    暂时先不提及ViewControllers里面的内容,暂时简单提及希望可以帮助理解。看官还可以注重看一下RAC 风格的数组操作.然后我要说的是上面第2步:

    这句代码是对CellTakerUserViewModel的初始化,并通过初始化的方式把TakeUserModel传入。联系上文可知初始化完成之后会返回一个CellTakerUserViewModel存放在RAC 风格的数组中,下面要看初始化CellTakerUserViewModel的方法实现:

    - (instancetype)initWithTakeUserModel: (TakeUserModel *) model
    
    {
            self = [super init];
            if (nil != self) {
                //1.也是RAC的操作,可以理解为一个聚合信号
                RAC(self, userModel) = [RACSignal combineLatest:@[RACObserve(model, take_user),RACObserve(model, money),RACObserve(model,luck_flg)] reduce:^id(NSString * takeuser){
                //2.这一个聚合信号是一个典型的RAC经典例子,希望我这么写会帮助不了解RAC的人更好的理解RAC
                    _userModel = [[TakeUserModel alloc]init];
                    _userModel.take_user =  model.take_user;
                    _userModel.money  =model.money;
                    _userModel.luck_flg =model.luck_flg;
                    DLog(@"mutableArrmutableArr%@",_userModel.luck_flg);
                    return _userModel;
                }];
            }
            return self;
    }
    

    RAC的风格数组执行起来像是一个for循环,一一遍历了数组中的每一个对象。其实每次遍历到不同的对象,对象的属性都会发生变化,那么RAC就会监测到。所以有了上面的聚合信号,聚合了TakeUserModel的三个属性,没当这些属性发生变化就会执行reduce里的方法,否则不会执行。执行完成之后返回一个TakeUserModel,并存放在RACSequence * newblogViewModels的array中。也完成了对上一篇标记处的解释。

    **
    联系以上步骤,我们在MVVMCheackLuckViewModel中完成了对CashRedBagModel的初始化。在CashRedBagModel中对CellTakerUserViewModel进行了初始化,我们在CellTakerUserViewModel中对TakeUserModel进行了初始化!

    在这个过程中我们让MVVMCheakLuckViewController中的下面一段代码中RACObserve(self.viewModel, cashModel)中的viewModel和大Model---->cashModel完成了初始化,所以才让subscribeNext后面Block中的代码成为了可能:

    [RACObserve(self.viewModel, cashModel) subscribeNext:^(id x) {
            [self initHeadView:self.viewModel.cashModel];
            if (self.viewModel.cashModel.take_userModelArr) {
                [self updateTabview];
            }
        }];
    

    我们在MVVMCheakLuckViewController中创建了TabelView只需要实现下面一段代码就完成了第二个界面的创建:

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
    只粘贴部分代………………
    CellTakerUserViewModel * vm = self.viewModel.cashModel.take_userModelArr[indexPath.row];
        TakeUserModel * userModel =vm.userModel;
        cell.textLabel.text = [NSString stringWithFormat:@"%@",userModel.take_user];
    }
    - (void)initHeadView:(CashRedBagModel *)model{
        UILabel *userLabel = (UILabel *)[self.view viewWithTag:219401];
        UILabel *cashNumberLabel = (UILabel *)[self.view viewWithTag:219402];
        NSString *userStr = [NSString stringWithFormat:@"%@的红包",model.ownerName];
        userLabel .text = userStr;
        cashNumberLabel.text = model.bonusDesc;
        _titleLabel.text = model.bless;
    }
    

    你会发现整个controller界面变得简单整洁,而且viewModel,Modle之间也相对比较独立,也可以随意复用。
    如果喜欢RAC的编程风格,请关注一下大家共同进步,抽时间会写一下RAC风格的登录页,到时会整理出一个完整的Demo,请见谅。

    相关文章

      网友评论

        本文标题:MVVM模式下如何使用ReactiveCocoa响应链式编程&l

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