MVVM+RAC项目实战用法

前言

因为公司项目的原因,开始接触MVVM+RAC的这种模式,刚开始并不是很适应这种函数式响应式的编程思想,感觉使用起来非常繁琐,大大的增加了开发的负担.但是随着自己学习的深入和项目的实践,这种模式的优点也随之显现.所以写这篇文章希望记录自己学习的过程,如果有写的不对的地方也希望大家指正.

本篇文章主要针对的是Objective-C语言来讲解ReactiveCocoa的应用,使用的也是公认最稳定的ReactiveCocoa v2.5,ReactiveCocoa在3.0以后的版本就是针对Swift的版本,所以大家可以根据自己需要来做下载.

目录

  • 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的一个子类,并且遵循了协议,意味着它既可以订阅信号,也能发送信号.

RACSubject的例子应用

//调用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的各位.

转载于:https://juejin.im/post/5b9b80a9e51d450e5a73e8b4

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值