• MVVM with ReactiveCocoa


    内容提要:

        本文首先对比MVC简单介绍了MVVM的概念和优点,其次,简单介绍了Reactive Cocoa的使用,最后,通过一个例子介绍了使用Reactive Cocoa的MVVM框架。

    正文:

      首先推荐2篇MVVM介绍的特别好的文章:《ReactiveCocoa 和 MVVM 入门》《MVVM Tutorial with ReativeCocoa

      查了一些资料,特别是看完这两篇长文章后,又自己做了实验,把自己认为的一些比较好的观点和理解,记录在自己的文章中,主要是供自己复习。

    1. MVVM框架

    (1)什么是MVVM?

      MVVM,即 M-V-VM (Model-View-ViewModel)

      先从下图的MVC和MVVM的对比主观理解一下。

       如下图MVC和MVVM的对比图,MVVM的整个框架从MVC的角度可以按下图理解:MVC的C部分可以分成2部分,一部分给了MVVM的VM(View-Model)部分,一部分给了MVVM的V(View)部分。(对比图中的第一行和第二行理解)

    另外,MVVM的V(View)部分,一部分是View Controller部分,一部分是View部分,也就是说整个MVVM的本质结构M+V+VMModel+View Controller、View+View Model


    (图片地址:https://github.com/SheronLv/Images/blob/master/MVVM/MVVM与MVC对比图.jpg)

    (2)为什么提出MVVM框架?

      MVC的缺点:ios Architecture, where MVC stands for Massive View Controller.

      MVVM的优点:This pattern, facilitated by Reactive Cocoa, provides an excellent alternative to MVC, and guarantees sleek and lightweight view controllers. 即MVVM是为了解决MVC中View Controller部分过于庞大,把MVC的Controller部分变的轻量级。

      除了上面view部分变的轻量级,View-Model部分对“应用世界”的依赖和影响也尽可能减少,View-Model部分的单元测试变的可行,是MVVM的另一个优点。

    2. Reactive Cocoa

      ReactiveCocoa is vital for binding the View and ViewModel together

    特点:Reactive Cocoa 将我们常见的异步信息流控制工具(比如:Delegate、Block callbacks、Target Action、KVO、Notifications)统一到RACSignal下。

       Reactive Cocoa can searve as a replacement for the delegate pattern, target-action pattern, key-value observing, and more.

    优点:Reactive Cocoa使用了更高层次的抽象。

    概念:

    (1)信号类RACSiganl:一些等待某事发生的代码,然后把结果值发送给他们的订阅者;

    (2)订阅者:是一段代码,它等待信号给它发送一些值,然后订阅者就能处理这些值了(如何订阅信号:调用信号RACSignal的subscribeNext就能订阅);

    (3)RACCommand:代表UI action,它包含一个signal和当前状态。这个signal是UI action的结果,状态表明action是否被执行。

     

    举几个例子来学习一下Reactive Cocoa的语法,具体的学习可以查阅其他资料:

    //@property (strong, nonatomic) RACCommand *executeSearch;
    
    //(1)map使用
    RACSignal *validSearchSignal = [[RACObserve(self, searchText)
                 //RACObserve创建一个信号Signal,this is a ReactiveCocoa wrapper over KVO(self.searchText是NSString类型)
                                     map:^id(NSString *text) {   // map操作,将text 转换成true和false的信号流
                            return @(text.length > 3);
                          }]
                          distinctUntilChanged];     //distinctUntilChanged保证,当state改变时,信号才被发出 
    
    //(2)subscribeNext使用
    [validSearchSignal subscribeNext:^(id x) { NSLog(@"search text is valid %@", x); }];//signal的信号放出来就是x的值
    //(3)RACCommand使用 self.executeSearch = [[RACCommand alloc] initWithEnabled:validSearchSignal signalBlock:^RACSignal *(id input) { return [self executeSearchSignal]; //此函数返回一个RACSignal }]; self.searchButton.rac_command = self.viewModel.executeSearch; //rac_command是UIButton的一个Reactive Cocoa属性 点击searchButton之后变成不可点击状态,需[self executeSearchSignal]函数执行完,返回执行完的状态,searchButton才又可点击。
    //(4)combineLatest、reduce使用
    // 绑定提交按钮可用状态的视图逻辑 

      RACSignal *requestingSingal = [RACObserve(self.viewModel, requestingQuesDetailInfo) distinctUntilChanged];

    [[[RACSignal combineLatest:@[requestQuesDetailInfoSuccessSignal, quesStateSignal]
    reduce:^id(NSNumber *requestQuesDetailInfoSuccess, NSString *quesState){
    BOOL hidden
    = (![requestQuesDetailInfoSuccess boolValue]
                                              || [quesState isEqualToString:@"已解决"]);  
                                                
    return @(hidden);
                                  }] distinctUntilChanged]
    subscribeNext:
    ^(NSNumber *hidden) {
    @strongify(self); self.commentTextView.hidden
    = [hidden boolValue];
    }];

    另外,RAC代替代理、KVO、监听事件、代替通知、监听文本变化等常用方法,举例如下:

    (参考:http://www.jianshu.com/p/87ef6720a096)

    // 1.代替代理
    //把调用某个对象的方法的信息转换成信号
     [[VC rac_signalForSelector:@selector(btnClick:)] subscribeNext:^(id x) {
            NSLog(@"点击按钮");
        }];
    
    // 2.KVO
     // 把监听redV的center属性改变转换成信号
    [[redV rac_valuesAndChangesForKeyPath:@"center" options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) 
            NSLog(@"%@",x);
        }];
    
     // 3.监听事件
     // 把按钮点击事件转换为信号,点击按钮,就会发送信号
        [[self.btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
    
            NSLog(@"按钮被点击了");
        }];
    
    // 4.代替通知
        // 把监听到的通知转换信号
        [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(id x) {
            NSLog(@"键盘弹出");
        }];
    
     // 5.监听文本框的文字改变
       [_textField.rac_textSignal subscribeNext:^(id x) {
    
           NSLog(@"文字改变了%@",x);
       }];
    
    // 6.处理多个请求,都返回结果的时候,统一做处理.
        RACSignal *request1 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    
            // 发送请求1
            [subscriber sendNext:@"发送请求1"];
            return nil;
        }];
    
        RACSignal *request2 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            // 发送请求2
            [subscriber sendNext:@"发送请求2"];
            return nil;
        }];
    
        // 使用注意:几个信号,参数一的方法就几个参数,每个参数对应信号发出的数据。
        [self rac_liftSelector:@selector(updateUIWithR1:r2:) withSignalsFromArray:@[request1,request2]];
    
    
    }
    // 更新UI
    - (void)updateUIWithR1:(id)data r2:(id)data1
    {
        NSLog(@"更新UI%@  %@",data,data1);
    }

    3.举例

     新建XYViewModel继承自NSObject,关键代码如下,此部分是MVVM的viewModel部分:

    //暴露的请求接口标志属性如下
    @property (nonatomic, assign) BOOL loading;
    @property (nonatomic, assign) BOOL loadingSuccess;
    @property (nonatomic, strong) NSString * errorMsg;
    //请求数据 - (void)requestData { self.loading = YES; self.loadingSuccess = NO; @weakify(self); [[.... cachePolicy:NVCacheTypeDisabled] subscribeNext:^(XYmodel * model) { @strongify(self); self.loading=NO; self.loadingSuccess=YES; } error:^(NSError * error) { @strongify(self); self.loading=NO; self.loadingSuccess=NO; self.errorMsg=[error nv_message]; }]; }

    ViewController里的关键代码如下,主要是绑定viewModel的信号部分:

    - (void)viewDidLoad
    {
    
        self.viewModel = [[XYViewModel alloc] init];
        [self bindViewModel];
        [self refreshData];
    }
    
    - (void)bindViewModel
    {
        //提示网络错误
        @weakify(self);
        [RACObserve(self.viewModel, errorMsg) subscribeNext:^(NSString * errorMsg) {
            @strongify(self);
            [self setShowLoading:NO];
            if(errorMsg&&errorMsg.length>0){
                [self showSplash:errorMsg];
            }
            [self.tableView reloadData];
            [self refreshFinished];
        }];
    
        //请求结束
        [RACObserve(self.viewModel, loading) subscribeNext:^(NSNumber * chaLoading) {
            @strongify(self);
            if ([chaLoading boolValue]==NO) {
                [self setShowLoading:NO];
            }
        }];
    
        //请求成功
        [RACObserve(self.viewModel, loadingSuccess) subscribeNext:^(NSNumber * loadingSuccess) {
            @strongify(self);
            if ([loadingSuccess boolValue] == YES) {
                [self setShowLoading:NO];
                [self.tableView reloadData];
                [self refreshFinished];
            }
        }];
    }

    这样就把ViewModel和ViewController绑定了起来。

    另外,ViewModel还需要暴露View要展示的数据供View使用。

  • 相关阅读:
    tesseract的简单使用
    快速添加请求头
    1010. 一元多项式求导 (25)
    1009. 说反话 (20)
    1008. 数组元素循环右移问题 (20)
    1007. 素数对猜想 (20)
    1006. 换个格式输出整数 (15)
    素数判断
    1002. 写出这个数 (20)
    1005. 继续(3n+1)猜想 (25)
  • 原文地址:https://www.cnblogs.com/Xylophone/p/5135617.html
Copyright © 2020-2023  润新知