MVVM+RAC项目实战用法

前言

由于公司项目的缘由,开始接触MVVM+RAC的这种模式,刚开始并非很适应这种函数式响应式的编程思想,感受使用起来很是繁琐,大大的增长了开发的负担.可是随着本身学习的深刻和项目的实践,这种模式的优势也随之显现.因此写这篇文章但愿记录本身学习的过程,若是有写的不对的地方也但愿你们指正.编程

本篇文章主要针对的是Objective-C语言来说解ReactiveCocoa的应用,使用的也是公认最稳定的ReactiveCocoa v2.5,ReactiveCocoa在3.0之后的版本就是针对Swift的版本,因此你们能够根据本身须要来作下载.json

目录

  • 1:MVVM由来
  • 2:RAC浅析
  • 3:实战使用

一:MVVM由来

你们都知道MVC是iOS App推荐的用来组织代码的权威规范,大部分的App也都遵循这样的构建,可是这样的设计模式却会随着项目的不断发展,业务逻辑的不断复杂让Controller变得臃肿,使得MVC从Model View Controller变成了Massive View Controller,这时传统的MVC设计模式已经不能知足咱们的需求.而MVVM的出现极大的解决了这一问题,他是MVC的进一步发展,将Controller里面的业务逻辑所有抽离到ViewModel里面,咱们只须要在Controller里面处理逻辑的回调结果便可.设计模式

固然MVVM使咱们的Controller完成了瘦身,可是ViewModel的出现,也使得咱们须要在Controller中引入ViewModel这个类,使得咱们所管理的类又多了一个,之间的交互就变得更加的麻烦.此时RAC的出现就正好接管这一套逻辑上的交互,用“信号流”的概念使得逻辑变得扁平化,咱们只要关心“信号流”的流向便可.数组

二:RAC浅析

RAC 中最核心的概念之一就是信号RACStream,RACStream中包含的两个子类——RACSignal 和 RACSequence.由于本篇文章只是介绍RAC在实战中的用法,因此会以RACSignal来介绍(好吧,实际上是由于笔者了解的太浅了-u-).若是想知道具体内部实现能够去看下霜神关于RAC源码解读的文章.网络

1:RACSignal

不说废话了,直接开干,首先来看一段咱们常见的signal建立->订阅->销毁信号的整个流程代码.并发

//建立信号
 RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
    //发送信号
    [subscriber sendNext:@"啊哈啦啦啦"];
    [subscriber sendCompleted];

    //取消订阅 能够选择在此作资源释放的操做
    return [RACDisposable disposableWithBlock:^{
        NSLog(@"signal dispose");
    }];
}];

RACDisposable *disposable = [signal subscribeNext:^(id x) {
    NSLog(@"subscribe value = %@",x);
    //输出结果: subscribe value = 啊哈啦啦啦
}];

   //取消订阅
[disposable dispose];
复制代码

内部逻辑实现: (1):RACSingal调用createSignal:建立信号,内部会去调用其子类RACDynamicSignal去建立信号. (2):RACDynamicSignal调用createSignal:方法,后面惟一的参数是个叫didSubscribe的block,当执行sendNext发送信号时,会将发送的内容保存在didSubscribe的block中. (3):signal信号执行subscribeNext方法,会把以前保存在didSubscribe的内容取出来. (4):取消订阅,执行disposableWithBlock这个block.函数

这样RACSignal的建立->订阅->销毁信号的一整个流程代码就完成了.这种只有当订阅者完成了订阅才会发送信号,因此咱们称其为冷信号.他就像是在一条生产线上,打开了机器,可是这个时候没有工人上班,那么工厂也不会正常运做.学习

2:RACSubject

经过查看源码咱们发现RACSubject是继承自RACSignal的一个子类,而且遵循了协议,意味着它既能够订阅信号,也能发送信号.atom

RACSubject的例子应用spa

//调用subject方法建立信号
复制代码

RACSubject *subject = [RACSubject subject];     //订阅信号 [subject subscribeNext:^(id x) {         NSLog(@"x = %@",x);     }];   //发送信号  [subject sendNext:@"啊哈啦啦啦"];

内部实现逻辑: (1):调用subject方法,建立信号.内部建立一个_subscribers可变数组,用来存储订阅信号的订阅者. (2):调用sendNext方法,发送消息.这时内部调用enumerateSubscribersUsingBlock方法对订阅者进行遍历,并发送消息. (3):全部订阅过改subject信号的订阅者会收到此消息,并完成打印x内容.

到这里RACSubject的一整套流程就完成了. RACSubject中无论有没有信号被订阅它都会去发送消息,这种特性的信号咱们称之为热信号.就比如工厂里的生产线一直在运做,有工人订阅了就会用数组存起来,等到有任务(消息)下发了,就会去执行这个任务.

3:RACCommand

查看源码咱们知道,RACCommand和以前的“信号流”概念不太同样,它是一个继承自NSObject的类,它的主要目的是为了管理和订阅RACSignal的类.在咱们作UI组件交互的时候, RACCommand可以帮助咱们更快的处理业务,下降代码的复杂度,节省开发的时间.

使用场景:监听按钮的点击事件、网络请求与回调处理.

知道了RACCommand的用途和使用场景,为了更好的理解RACCommand,咱们先来看看RACCommand的两个初始化方法和执行方法:

初始化方法:

- (id)initWithSignalBlock:(RACSignal * (^)(id input))signalBlock;
- (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock;
复制代码

执行方法:

- (RACSignal *)execute:(id)input;
- (void)setRac_command:(RACCommand *)command;
复制代码

方法介绍: 1:咱们知道RACCommand的做用是管理RACSignal的信号,因此初始化方法的signalBlock的返回类型就是咱们须要管理的RACSignal,他的入参input,就是咱们执行该Command时所传入的数据. 2:初始化第二个方法较第一个方法多了个RACSignal类型的参数enabledSignal;这个参数的目的主要是为了过滤信号,只有当该信号中传递的参数为真时, Command才可以被执行. 3:执行方法中第一个方法是RACCommand里面用于执行的方法,直接调用便可. 4:执行方法中第二个方法是UIButton的分类方法,具体使用后面会作介绍.

知道了MVVM、RACSingal、RACSubject、RACCommand的介绍和用法,接下来咱们就能够在实际项目中进行应用了.

三:实战使用

首先咱们须要在ViewModel.h文件中声明一个command,用于管理咱们的信号.

//声明属性testCommand
@property (nonatomic, strong) RACCommand *testCommand;
复制代码

注意:这里声明的testCommand必须使用strong修饰强引用,不然接受不到RACCommand内部的信号.

而后ViewModel.m文件中在get方法中进行初始化操做.

//testCommand
- (RACCommand *)testCommand
{
    if (!_testCommand) {
    
      _testCommand = [[RACCommand alloc]initWithSignalBlock:^RACSignal *(id input) {
        
          return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            
              //须要传递的参数,若是传入的input就是网络请求须要的参数,直接传input便可
              NSDictionary *sendParams = @{@"test":@"我是啊哈啦啦啦"};
            
              //在这里面进行网络请求的操做,笔者本身把网络请求封装成了一个信号,方便订阅处理.
             [[YWApiManager sendApi:SCApiTypeTest withParam:sendParams] subscribeNext:^(NSDictionary *json{
                  //json:是网络请求回调后,转换后取得的json
                  [subscriber sendNext:json];
                 //必定要加上sendCompleted这个方法,否则没法再次执行该command
                  [subscriber sendCompleted];
                
              } error:^(NSError *error) {
               //错误信息 sendError 内部已经取消订阅信号 不用执行sendCompleted方法
                  [subscriber sendError:error];
              }];
            
             return nil;
      
         }];
        
     }];
    
 }
      return _testCommand;
}
复制代码

上面的方法中,笔者直接采用了initWithSignalBlock这个方法初始化RACCommand,若是说你在执行方法时已将须要须要传递的参数字典传入,那么能够直接将input当成sendParams传入. 注意:在发送消息后,必定要执行[subscriber sendCompleted]; 表示发送消息已经结束,取消信号的订阅.否则的话该command会一直处于执行中,不能再次执行该command.

写到这里 已经成功的将咱们Controller中的网络请求和回调处理好了,接下来咱们须要在Controller里面对信号发送的json进行处理,看下面Controller中的代码.

首先咱们须要在Controller中导入ViewModel,而且声明对象viewModel.具体操做看下面的代码

[self.viewModel.testCommand.executionSignals subscribeNext:^(RACSignal * _Nullable execution) {
    
    [execution subscribeNext:^(id x) {
        //x为网络请求的回调结果,能够在这里对数据进行处理
        NSLog(@"json = %@",x);
    }];
    
}];
复制代码

使用testCommand的executionSignals信号进行订阅操做. executionSignals是一个内部装有RACSignal的高阶信号,因此咱们对他进行降阶操做拿到execution信号,并再次订阅此信号,此时入参的x就是咱们以前传递的网络请求回调“json”.

若是咱们在非并发RACCommand中咱们能够用switchToLatest进行降阶操做,这样写比较直观,也是笔者在项目中经常使用的方法.

[self.viewModel.testCommand.executionSignals.switchToLatest subscribeNext:^(id x) {
    //x为网络请求的回调结果,能够在这里对x作处理,修改UI
    NSLog(@"json = %@",x);
}];
复制代码

对于错误信号的订阅:

[self.viewModel.testCommand.errors subscribeNext:^(NSError *error) {
    NSLog(@"error = %@",error);
}];
复制代码

注意:咱们不该该使用subscribeError:这个方法取订阅错误信号,由于executionSignals这个信号是不会发送error事件的.因此需使用subscribeNext:订阅错误信号.

最后咱们只要执行该方法就好了,执行代码地方传的参数能够为空,或者传入须要用到的参数,这个能够根据需求本身来决定.

[self.viewModel.testCommand execute:@"啊哈啦啦啦"];
复制代码

而后咱们再来看看第二种执行方法,这种方法会使按钮绑定上testCommand,若是RACCommand是以initWithEnabled这种方式初始化的,按钮的enabled属性会随enabledSignal传入的值的改变而改变.即传入值为真,按钮不可点击.

testButton.rac_command = self.viewModel.testCommand;
复制代码

以上代码都是针对冷信号来处理,让咱们在看下RACSubject在项目中的用法.

应用场景:好比如今咱们有个tableView的列表,每一个cell的点击事件跳到新的界面,在新的界面中咱们会选择一些数据并回传到以前的界面,最后刷新tableView,把选择的数据展现在tableView上.这样的一个操做咱们就可使用RACSubject来完成,看下面代码.

@property (nonatomic, strong) RACSubject *reloadSignal;
复制代码

首先我在ViewModel里面声明了一个热信号reloadSignal,而后初始化testCommand.

//testCommand
- (RACCommand *)testCommand
{
     if (!_testCommand) {
         @weakify(self);
     _testCommand = [[RACCommand alloc]initWithSignalBlock:^RACSignal *(id input) {
        
        return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            
                [subscriber sendNext:@"我是阿哈啦啦啦,我要被发送了"];
                [subscriber sendCompleted];
                return nil;
            
            }]doNext:^(id x) {
               @strongify(self);
               //doNext的入参x是sendNext发送的参数
               [self.reloadSignal sendNext:x];
            
           }];
        
        }];
    
    }
      return _testCommand;
}
复制代码

和第一个例子不一样的是,这里发送的数据会进行一步操做,调用doNext:方法(将sendNext的参数传递给doNext的入参).而后热信号reloadSignal发送入参x.

最后在Controller中的操做,和例1中是同样的,要注意的是调用时须要使用reloadSignal进行订阅.热信号的优势在于,对于须要进行屡次reload的这种操做,咱们不用去重复订阅.

[self.viewModel.reloadSignal subscribeNext:^(id x) {
    NSLog(@"x = %@",x);
}];
复制代码

最后

关于RAC这块笔者本身还在学习之中,因此但愿抛砖引玉,你们互相讨论共同进步.以上就是关于ReactiveCocoa的一个简单用法,比较简单实用,但愿能帮到新学习RAC的各位.

相关文章
相关标签/搜索