ReactiveCocoa学习记 (1)

2016-07-14  本文已影响67人  07212a79db66

ReactiveCocoa(简称为RAC),是由Github开源的一个应用于iOS和OS开发的新框架,由于它结合了响应式编程思想(Reactive Programming):(就是不考虑调用的顺序,只考虑结果)和函数式编程思想(Functional Programming):(就是把操作尽量写成一系列嵌套的函数或者方法调用,每个方法必须有返回值(本身对象),把函数或者Block当做参数,block参数(需要操作的值)block返回值(操作结果)),所以ReactiveCocoa又称之为函数响应式编程(FRP)框架。

1. 导入框架

使用CocoaPods导入:


2.常用的类

(1) RACSiganl

(2) RACSiganl的简单使用

    // RACSignal使用步骤: 1.创建信号  2.订阅信号 3.发送信号
    // RACSignal:有数据产生的时候,就使用RACSignal
    //执行的流程
    // 1.创建信号,首先把didSubscribe保存到信号中,还不会触发。
    // 2.当信号被订阅,也就是调用signal的subscribeNext:nextBlock
    // 2.2 subscribeNext内部会创建订阅者subscriber,并且把nextBlock保存到subscriber中。
    // 2.1 subscribeNext内部会调用siganl的didSubscribe
    // 3.siganl的didSubscribe中调用[subscriber sendNext:@1];
    // 3.1 sendNext底层其实就是执行subscriber的nextBlock
    //1 创建信号
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        
        //每当有订阅者信号,就会来到此block
        //RACSubscriber:表示订阅者的意思,用于发送信号,这是一个协议,不是一个类,只要遵守这个协议,并且实现方法才能成为订阅者。通过create创建的信号,都有一个订阅者,帮助他发送数据。
        //3 发送数据
        [subscriber sendNext:@"发送数据"];
        
        // 如果不在发送数据,最好发送信号完成,内部会自动调用[RACDisposable disposable]取消订阅信号。
        [subscriber sendCompleted];
        
        return [RACDisposable disposableWithBlock:^{
            //当信号发送完成或者发送错误,就会自动执行这个block,取消订阅信号。执行完Block后,当前信号就不在被订阅了。
            NSLog(@"信号被销毁");
        }];
    }];
    
    
    //2 创建订阅者
    [signal subscribeNext:^(id x) {
        //每当有信号发出数据,就会来到此block
        NSLog(@"%@",x);
    }];

2 RACDisposable

(1)简单使用

#import "ViewController.h"
#import "ReactiveCocoa.h"

@interface ViewController ()
@property (nonatomic, strong) id<RACSubscriber> subscriber;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    // 1.创建信号
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber ) {
        
        _subscriber = subscriber;
        
        // 3.发送信号
        [subscriber sendNext:@"123"];
        
        return [RACDisposable disposableWithBlock:^{
            // 只要信号取消订阅就会来这
            // 清空资源
            NSLog(@"信号被取消订阅了");
        }];
    }];
    
    // 2.订阅信号
    RACDisposable *disposable = [signal subscribeNext:^(id x) {
        
        NSLog(@"%@",x);
        
    }];
    // 默认一个信号发送数据完毕们就会主动取消订阅.
    // 只要订阅者在,就不会自动取消信号订阅
    // 取消订阅信号
    [disposable dispose]; //取消订阅信号


3 RACSubject

    //RACSubject使用步骤
    // 1.创建信号 [RACSubject subject],跟RACSiganl不一样,创建信号时没有block。
    // 2.订阅信号 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
    // 3.发送信号 sendNext:(id)value

    // RACSubject:底层实现和RACSignal不一样。RACSubject底层是遍历所有的订阅者,调用nextBlock
    // 1.调用subscribeNext订阅信号,只是把订阅者保存起来,并且订阅者的nextBlock已经赋值了。
    // 2.调用sendNext发送信号,遍历刚刚保存的所有订阅者,一个一个调用订阅者的nextBlock。

    // 1.创建信号
    RACSubject *subject = [RACSubject subject];

    // 2.订阅信号
   //RACSubject被订阅,仅仅是保存订阅者
    [subject subscribeNext:^(id x) {
        // block调用时刻:当信号发出新值,就会调用.
        NSLog(@"第一个订阅者%@",x);
    }];
    [subject subscribeNext:^(id x) {
        // block调用时刻:当信号发出新值,就会调用.
        NSLog(@"第二个订阅者%@",x);
    }];

    // 3.发送信号
  //RACSubject发送数据,遍历所有的订阅,调用他们的nextBlock
    [subject sendNext:@"1"];

(2)代替代理
新建一个继承自UIView的MyHeaderView,该view中有一个按钮,当点击按钮的时候通知当前控制器做一些处理

#import <UIKit/UIKit.h>
#import "ReactiveCocoa.h"

@interface MyHeaderView : UIView
@property (nonatomic, strong) UIButton *btn;
@property (nonatomic, strong) RACSubject *clickSignal;
@end
#import "MyHeaderView.h"

@implementation MyHeaderView
- (instancetype)initWithFrame:(CGRect)frame {
    if (self == [super initWithFrame:frame]) {
        _btn = [UIButton buttonWithType:UIButtonTypeCustom];
        _btn.frame = CGRectMake(20, 40, 100, 30);
        _btn.backgroundColor = [UIColor redColor];
        [_btn setTitle:@"我是按钮" forState:UIControlStateNormal];
        [_btn addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside];
        [self addSubview:_btn];
    }
    return self;
}

- (RACSubject *)clickSignal {
    if (_clickSignal == nil) {
        _clickSignal = [RACSubject subject];
    }
    return _clickSignal;
}

- (void)btnClick:(UIButton *)sender {
    [self.clickSignal sendNext:@"按钮被点击了"];
}

@end

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
  
    MyHeaderView *header = [[MyHeaderView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 100)];
 
    [self.view addSubview:header];
    
    [header.clickSignal subscribeNext:^(id x) {
       
        NSLog(@"点击了按钮%@",x);
    }];
 }
 

4 RACReplaySubject
(1)使用步骤
1.创建信号 [RACSubject subject],跟RACSiganl不一样,创建信号时没有block。
2.可以先订阅信号,也可以先发送信号。
2.1 订阅信号 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
2.2 发送信号 sendNext:(id)value

//执行过程:
//1.调用sendNext发送信号,把值保存起来,然后遍历刚刚保存的所有订阅者,一个个调用订阅者的nextBlock。
//2.调用subscribeNext订阅信号,遍历保存的所有值,一个一个调用订阅者的nextBlock
//如果想要当一个信号被订阅,就重复播放之前所有值,需要先发送信号,再订阅信号。也就是先保存值,再订阅
 RACReplaySubject *subject = [RACReplaySubject subject];
    //发送信号
    [subject sendNext:@"123"];
    
    //订阅信号
    [subject subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
  

5 RACTuple和 RACSequence

(1)简单使用

  1. 数组和字典
    // 元组
    RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:@[@"213",@"321",@1]];
    NSString *str = tuple[0];
    NSLog(@"%@",str);
  
    
    // 数组遍历
    NSArray *arr = @[@"213",@"321",@1];
    
    // RAC集合
    //      把数组转换成集合RACSequence
    //    RACSequence *sequence = arr.rac_sequence;
    //
    //    // 把集合RACSequence转换RACSignal信号类
    //    RACSignal *signal = sequence.signal;
    //
    //    // 订阅集合信号,内部会自动遍历所有的元素发出来
    //    [signal subscribeNext:^(id x) {
    //        NSLog(@"%@",x);
    //    }];
    //也可以写为:
    [arr.rac_sequence.signal subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    
    
    //字典
       //字典
    NSDictionary *dict = @{@"name":@"张三",@"age":@20};
    
    //转为集合
    [dict.rac_sequence.signal subscribeNext:^(id x) {
        //取值
        //NSString *key = x[0];
        //NSString *value = x[1];
        //NSLog(@"%@ ---- %@",key,value);
        
        //或者使用宏
        RACTupleUnpack(NSString *key,NSString *value) = x;
        NSLog(@"%@ ---- %@",key,value);
        
        
    }];
    

2)字典转模型

 NSString *filePath = [[NSBundle mainBundle] pathForResource:@"flags.plist" ofType:nil];
    NSArray *dictArr = [NSArray arrayWithContentsOfFile:filePath];
    
    NSMutableArray *arr = [NSMutableArray array];
    [dictArr.rac_sequence.signal subscribeNext:^(NSDictionary *x) {
        Flag *flag = [Flag flagWithDict:x];
        [arr addObject:flag];
    }];
    
    //或者使用下面的方法
      // 会把集合中所有元素都映射成一个新的对象
      //当信号被订阅,会遍历集合中的原始值,映射成新值,并且保存到新的数组里。
   NSArray *arr = [[dictArr.rac_sequence map:^id(NSDictionary *value) {
        // value:集合中元素
        // id:返回对象就是映射的值
        return [Flag flagWithDict:value];
    }] array];
    
    NSLog(@"%@",arr);

6 RACMulticastConnection

(1)使用步骤:
1.创建信号 + (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe
2.创建连接 RACMulticastConnection *connect = [signal publish];
3.订阅信号,注意:订阅的不在是之前的信号,而是连接的信号。 [connect.signal subscribeNext:nextBlock]
4.连接 [connect connect]

  RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
     
      NSLog(@"发送请求");
      return nil;
  }];
    
    //订阅信号
    [signal subscribeNext:^(id x) {
        NSLog(@"接收数据");
    }];


    [signal subscribeNext:^(id x) {
        NSLog(@"接收数据");
    }];

运行程序,会执行两遍发送请求,也就是每次订阅都会发送一次请求,此时使用RACMulticastConnection解决重复请求问题

  // 1.创建信号
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {


        NSLog(@"发送请求");
        [subscriber sendNext:@1];

        return nil;
    }];

    // 2.创建连接
    RACMulticastConnection *connect = [signal publish];

    // 3.订阅信号,
    // 注意:订阅信号,也不能激活信号,只是保存订阅者到数组,必须通过连接,当调用连接,就会一次性调用所有订阅者的sendNext:
    [connect.signal subscribeNext:^(id x) {

        NSLog(@"订阅者一信号");

    }];

    [connect.signal subscribeNext:^(id x) {

        NSLog(@"订阅者二信号");

    }];

    // 4.连接,激活信号
    [connect connect];

7 RACCommand

(1)简单的使用

 // 一、RACCommand使用步骤:
// 1.创建命令 initWithSignalBlock:(RACSignal * (^)(id input))signalBlock
// 2.在signalBlock中,创建RACSignal,并且作为signalBlock的返回值
// 3.执行命令 - (RACSignal *)execute:(id)input

// 二、RACCommand使用注意:
// 1.signalBlock必须要返回一个信号,不能传nil.
// 2.如果不想要传递信号,直接创建空的信号[RACSignal empty];
// 3.RACCommand中信号如果数据传递完,必须调用[subscriber sendCompleted],这时命令才会执行完毕,否则永远处于执行中。

// 三、RACCommand设计思想:内部signalBlock为什么要返回一个信号,这个信号有什么用。
// 1.在RAC开发中,通常会把网络请求封装到RACCommand,直接执行某个RACCommand就能发送请求。
// 2.当RACCommand内部请求到数据的时候,需要把请求的数据传递给外界,这时候就需要通过signalBlock返回的信号传递了。

// 四、如何拿到RACCommand中返回信号发出的数据。
// 1.RACCommand有个执行信号源executionSignals,这个是signal of signals(信号的信号),意思是信号发出的数据是信号,不是普通的类型。
// 2.订阅executionSignals就能拿到RACCommand中返回的信号,然后订阅signalBlock返回的信号,就能获取发出的值。

// 五、监听当前命令是否正在执行executing

// 六、使用场景,监听按钮点击,网络请求
  // RACCommand:处理事件
   // RACCommand:不能返回一个空的信号
   // 1.创建命令
   RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
       // input:执行命令传入参数
       // Block调用:执行命令的时候就会调用
       NSLog(@"%@",input);
       
       return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
           
           // 发送数据
           [subscriber sendNext:@"执行命令产生的数据"];
           
           return nil;
       }];
   }];
   
   // 如何拿到执行命令中产生的数据
   // 订阅命令内部的信号

   
   // 2.执行命令
   RACSignal *signal = [command execute:@1];
   
   // 3.订阅信号
   [signal subscribeNext:^(id x) {
       NSLog(@"%@",x);
   }];

    // 1.创建命令
    RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
        // input:执行命令传入参数
        // Block调用:执行命令的时候就会调用
        NSLog(@"%@",input);
        
        return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            
            // 发送数据
            [subscriber sendNext:@"执行命令产生的数据"];
            
            return nil;
        }];
    }];
    
    // 订阅RACCommand中的信号
    // 注意:必须要在执行命令前,订阅
    // executionSignals:信号源,信号中信号,signalOfSignals:信号:发送数据就是信号
    //    [command.executionSignals subscribeNext:^(RACSignal *x) {
    //
    //        [x subscribeNext:^(id x) {
    //            NSLog(@"%@",x);
    //        }];
    //
    //    }];
    
    // switchToLatest获取最新发送的信号,只能用于信号中信号
    [command.executionSignals.switchToLatest subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    
    // 2.执行命令
    [command execute:@1];
//switchToLatest
 // 创建信号中信号
    RACSubject *signalOfSignals = [RACSubject subject];
    RACSubject *signalA = [RACSubject subject];
    RACSubject *signalB = [RACSubject subject];
    
    // 订阅信号
    //    [signalOfSignals subscribeNext:^(RACSignal *x) {
    //        [x subscribeNext:^(id x) {
    //            NSLog(@"%@",x);
    //        }];
    //    }];
    // switchToLatest:获取信号中信号发送的最新信号
    //switchToLatest:用于signal of signals,获取signal of signals发出的最新信号,也就是可以直接拿到RACCommand中的信号
    [signalOfSignals.switchToLatest subscribeNext:^(id x) {
        
        NSLog(@"%@",x);
    }];
    
    // 发送信号
    [signalOfSignals sendNext:signalA];
    
    [signalA sendNext:@1];
    [signalB sendNext:@"BB"];
    [signalA sendNext:@"11"];
 // 当前命令内部发送数据完成,一定要主动发送完成
    // 1.创建命令
    RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
        // input:执行命令传入参数
        // Block调用:执行命令的时候就会调用
        NSLog(@"%@",input);
        
        return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            
            // 发送数据
            [subscriber sendNext:@"执行命令产生的数据"];
            
            // 发送完成
            [subscriber sendCompleted];
            
            return nil;
        }];
    }];
    
    // 监听事件有没有完成
    [command.executing subscribeNext:^(id x) {
        if ([x boolValue] == YES) { // 当前正在执行
            NSLog(@"当前正在执行");
        }else{
            // 执行完成/没有执行
            NSLog(@"执行完成/没有执行");
        }
    }];
    // 2.执行命令
    [command execute:@1];
    

8 ReactiveCocoa常见的用法
(1)KVO

[[_headView rac_valuesForKeyPath:@"frame" observer:nil] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];

(2)监听事件,例如监听按钮的点击

    [[_headView.btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
    NSLog(@"%@",x);
    }];

(3)代替通知

    [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(id x) {
    NSLog(@"%@",x);
    }];

(4)监听文本框

    [_headView.textField.rac_textSignal subscribeNext:^(id x) {
    
    NSLog(@"%@",x);
    }];
    

(5)当一个界面有多个网络请求的时候,全部请求完成之后刷新界面

- (void)networkRequest
{
    // 当一个界面有多次请求时候,需要保证全部都请求完成,才搭建界面
    
    // 请求热销模块
    RACSignal *hotSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        
        // 请求数据
        // AFN
        NSLog(@"请求数据热销模块");
        
        [subscriber sendNext:@"热销模块的数据"];
        
        return nil;
    }];
    
    // 请求最新模块
    RACSignal *newSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        // 请求数据
        NSLog(@"请求最新模块");
        
        [subscriber sendNext:@"最新模块数据"];
        
        return nil;
    }];
    
    // 数组:存放信号
    // 当数组中的所有信号都发送数据的时候,才会执行Selector
    // 方法的参数:必须跟数组的信号一一对应
    // 方法的参数;就是每一个信号发送的数据
    [self rac_liftSelector:@selector(updateUIWithHotData:newData:) withSignalsFromArray:@[hotSignal,newSignal]];
}

- (void)updateUIWithHotData:(NSString *)hotData newData:(NSString *)newData
{
    // 拿到请求的数据
    NSLog(@"更新UI %@ %@",hotData,newData);
}

9 宏
(1) RAC(TARGET, [KEYPATH, [NIL_VALUE]]):用于给某个对象的某个属性绑定。

 // 只要文本框文字改变,就会修改label的文字
    RAC(self.labelView,text) = _textField.rac_textSignal;

(2)RACObserve(self, name):监听某个对象的某个属性,返回的是信号

[RACObserve(self.view, center) subscribeNext:^(id x) {

        NSLog(@"%@",x);
    }];

(3)@weakify(Obj)和@strongify(Obj),一般两个都是配套使用,解决循环引用问题

- (void)viewDidLoad
{
    [super viewDidLoad];

    @weakify(self);
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
       
        @strongify(self)
        
        NSLog(@"%@",self);
        
        return nil;
    }];
    _signal = signal;
    
}

(4)RACTuplePack:把数据包装成RACTuple(元组类)

 // 包装元组
    RACTuple *tuple = RACTuplePack(@1,@2);
    
    NSLog(@"%@",tuple[0]);

(5)RACTupleUnpack:把RACTuple(元组类)解包成对应的数据。

RACTuple *tupe  =  RACTuplePack(@1,@2);
    RACTupleUnpack(NSString *obj1, NSString *obj2) = tupe;
    NSLog(@"%@--%@",obj1,obj2);
    
上一篇下一篇

猜你喜欢

热点阅读