ReactiveCocoa学习笔记

25 篇文章 0 订阅
3 篇文章 0 订阅

最近在学习RAC框架,断断续续的看了好久,看的文章博客可以说少说也得有一两百,所以这是一个综合的产物,也可以算是一个学习笔记,毕竟好记性不如烂笔头,记录下来,以供以后复习。如有侵权,联系立删。

书归正传:

RAC简介

RAC是一个F(functional)R(reactive)P(programming)框架,可以很好的结合MVVM. 写出的项目易于维护。但是导入MVVM和RAC的项目,学习成本有点高,如果自己独立开发,或者团队内成员水平较平均且普遍较高,那么可以尝试这么做。

注意:

RAC框架已经有几年的历史了,理所当然的有OC,Swift版本,从2.5版本之后,就开始全面支持swift,不再支持OC,所以如果你的项目是OC的项目,请使用cocoapods导入2.5或之前的版本,如果你对cocoapods不甚了解,请移步我的cocoapods踩坑:

另外需要了解的:

MVVM设计模式。

相信大家对MVC肯定都很熟悉,Model业务模型,View前端显示,Controller控制器。
但是一般我们在开发的时候,(仅针对iOS开发)M层一般只会写有几个成员属性,也就是说,所有的业务逻辑,网络层代码之类的,全部都写在了Controller中,这样,就造成了C层中有大量的,冗杂的代码,让人维护起来很费劲,本着‘高内聚,低耦合’的原则,有些大神们就想到了MVVM的设计模式。

本人理解

如有不对,还请各位大神批评指正。
MVVM其实是M-V-C-VM,也就是比正常的MVC多了一个ViewModel,
VM从网络请求数据,然后参照Model的格式和一些需求,处理数据,然后返回给Controller,再传递给View展示出来,大概就是图中的画出来的样子
这里写图片描述

ViewModel主要从事的业务
1. 从网络请求、处理数据,然后返回给Controller,controller再传递给View层显示
2. 界面中的业务逻辑
也就是把以前Controller中的逻辑代码,换了个地方,全部写在了ViewModel中了。
所以,从根本上来说,MVC和MVVM是可以相互转换的。

下面从使用中来了解RAC。

重要的类

RACSignal

这个是RAC中最重要的一个类了(RAC中全部都是以信号来处理的)
信号分为热信号和冷信号,默认创建完成之后是冷信号,这个时候是无用的,也就是值改变了,也不会触发,只有订阅subscribe了之后,信号才能变成热信号,订阅的block(subscribeNext)才会被调用。

创建信号的方法:

+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe;
可以看到返回了一个block,该block返回值为RACDisposable,参数为subscriber,该参数遵守RACSubscriber协议

  • RACDisposable,如果想要取消订阅,使用此类的方法- (void)dispose;
  • RACSubscriber协议中的方法有:
    1. - (void)sendNext:(id)value;
    2. - (void)sendError:(NSError *)error;
    3. - (void)sendCompleted;
      用来传值,或者返回数据。
订阅信号的方法

- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
该方法是RACSignalsubscription类别中的一个方法。
调用之后,冷信号变成热信号,nextBlock会执行
使用中要注意:
1. 在block中,如果订阅之后,不想传值,可以使用协议中的方法
[subscriber sendNext:nil],若想传,则在该方法后传入参数(必须id类)
2. 传值完成之后,必须调用sendCompleted方法,以示订阅完成,否则,无法再次订阅信号。
3. 如果返回值为ERROR,且要传递,调用sendError方法,sendNext方法无法传递错误信息

了解了信号和订阅,我们就可以开始简单的使用一下了。
假如界面中有一个textview一个label. 我们可以绑定他们两个,以达到用户在textview中输入的值,显示在label中

    //界面的布局之类的不再写,只写下逻辑块代码
    [self.textview.rac_textSignal subscribeNext:^(id x) {
        self.label.text = (NSString *)x;
    }];

代码中的rac_textSignal返回的是textView的文本信号。我们订阅之后,就可以获得这个信号,其中的id x就是textview中的文本,所以我们直接赋值给label.text就OK了

RACCommand

这个类,是我花了好久时间才看懂的一个类。主要从事事件的处理。
比如网络请求这种,一方传递出去一个信号,需要另一方返回一个信号的这种流程。前后端通信这种就是一个很好的例子: 前端post数据给后端,然后服务器返回给我们相应的JSON字符串,这个就可以使用RACCommand来处理。
与RACSignal的区别, RACSignal是单向的,只能后者订阅前者,反之则不行

创建方法
  • - (id)initWithSignalBlock:(RACSignal * (^)(id input))signalBlock;
    该方法只有一个 block参数,创建的时候,要在block中创建一个RACSignal,并返回,如果不想返回信号,则可以return [RACSignal empty];来返回一个空的信号。
  • - (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock;
    该方法除了一个 block参数之外,还有一个enable信号,用来控制command能否执行。
执行方法

- (RACSignal *)execute:(id)input;
只有执行了,订阅的block才会调用

属性
  • RACCommand有几个属性
    1. executionSignals
    2. executing
    3. errors
      这些属性的类型都是RACSignal. 目的就是在执行之后,可以订阅,查看信号内的内容。
    4. allowsConcurrentExecution BOOL类型的变量,是否允许同时多个执行,默认是NO
注意:

一般情况下,[self.command.executionSignals subscribeNext:^(id x) {...}]订阅的时候,里边的x,是RACSignal类的实例,我们此时订阅这个信号,就可以看到我们发出的信号(外层信号)的内容,如果想要看到里边包装的信号,也就是信号中的信号,我们可以使用

  • 双重订阅
[self.command.executionSignals subscribeNext:^(RACSignal *x) {
    [x subscribeNext:^(id x){
        NSLog(@"%@",x);
    }];
}]
  • RAC的高级用法
// switchToLatest 获取信号中信号最近发出信号,订阅最近发出的信号。
[[self.command.executionSignals switchToLatest] 
subscribeNext:^(id x) {
    NSLog(@"%@",x);        
}];

switchToLatest方法: 获取信号中信号最近发出信号,订阅最近发出的信号。也就是可以跳过外层的信号,直接订阅内部的信号。

或者

//跳过第一次订阅,直接订阅第二次
[[self.command.executionSignals skip:1] subscribeNext:^(id x) {
       NSLog(@"%@",x);   
}];

RACCommand的用法

//1,先创建
RACCommand *cmd = [[RACCommand alloc]initWithSignalBlock:^RACSignal *(id input) {
        return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            [subscriber sendNext:@"传值"];
            [subscriber sendCompleted];
            return nil;
        }];
    }];

    //2. 订阅
    //信号中的信号,要双层订阅
    [cmd.executionSignals subscribeNext:^(id x) {
        NSLog(@"%@",x);  //<RACDynamicSignal: 0x79e99050> name: 
        [x subscribeNext:^(id inner) {
            NSLog(@"%@",inner);  //传值
        }];
    }];

    //开始订阅的时候会打Log
    [cmd.executing subscribeNext:^(id x) {
        NSLog(@"executing");
    }];

    //如果出错了,会走这里
    [cmd.errors subscribeNext:^(id x) {
        NSLog(@"error");
        NSLog(@"%@",x);
    }];

    //3. 执行
    [cmd execute:@"执行"];

RACSubject

介绍

RACSubject:信号提供者,自己可以充当信号,又能发送信号。

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

替换代理,block

//在第二个VC里边声明一个RACSubject属性,以反向传值为例,第二个页面的背景色为随机色,点击按钮POP回第一个页面,然后把颜色传给第一个页面
@interface SecViewController : UIViewController

@property (nonatomic,strong) RACSubject *delegateSubject;

@end

@implementation SecViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    UIButton *btn = [[UIButton alloc]init];
    [btn setTitle:@"点击传值" forState:UIControlStateNormal];
    btn.frame = CGRectMake(0, 0, 100, 44);
    btn.center = self.view.center;
    [[btn rac_signalForControlEvents:UIControlEventTouchUpInside]
     subscribeNext:^(id x) {
         if (self.delegateSubject) {
             [self.delegateSubject sendNext:self.view.backgroundColor];
             [self.delegateSubject sendCompleted];
         }
         [self.navigationController popViewControllerAnimated:YES];
     }];
    [self.view addSubview:btn];
}

//第一个页面的PUSH代码
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    SecViewController *sec = [[SecViewController alloc]init];
    sec.delegateSubject = [RACSubject subject];
    [sec.delegateSubject subscribeNext:^(id x) {
        NSLog(@"%@",x);
        self.view.backgroundColor = (UIColor *)x;
    }];
    sec.hidesBottomBarWhenPushed = YES;
    [self.navigationController pushViewController:sec animated:YES];
}

可以在代码段里看到一个[btn rac_signalForControlEvents:UIControlEventTouchUpInside],这个方法会返回按钮的command中的信号,系统默认为我们做的,我们只要订阅这个信号,就可以执行按钮对应的Action了

RACTuple

RAC中的元组类,用来包装值,类似NSArray

创建方法
+ (instancetype)tupleWithObjectsFromArray:(NSArray *)array;
+ (instancetype)tupleWithObjectsFromArray:(NSArray *)array convertNullsToNils:(BOOL)convert;
+ (instancetype)tupleWithObjects:(id)object, ... NS_REQUIRES_NIL_TERMINATION;
属性
@property (nonatomic, readonly) NSUInteger count;

/// These properties all return the object at that index or nil if the number of 
/// objects is less than the index.
@property (nonatomic, readonly) id first;
@property (nonatomic, readonly) id second;
@property (nonatomic, readonly) id third;
@property (nonatomic, readonly) id fourth;
@property (nonatomic, readonly) id fifth;
@property (nonatomic, readonly) id last;

所以我们可以使用索引来获取Tuple中的值

    NSArray *testArr = @[@"1",@"2",@"3",@"4"];
    RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:testArr];
    NSLog(@"%@",tuple[0]);    //1
    NSLog(@"%@",tuple[1]);    //2
    NSLog(@"%@",tuple.third); //3

如果遇到NSDictionary,则RACTuple则会循环键值对的个数次,倒叙的遍历字典的键值对(通常是把一个键值对当成数组的一个元素)

        NSDictionary *testDic = @{@"name":@"testData",@"age":@(20)};
        [testDic.rac_sequence.signal subscribeNext:^(RACTuple *x) {
            NSLog(@"%@",x);               
            NSLog(@"%ld",(long)x.count);  
            NSLog(@"%@",x[0]);            
            NSLog(@"%@",x[1]);            
            NSLog(@"%@",x.third);         
        }];

循环 第一次,log结果:(<RACTuple: 0x79e49550> (age,20)   2   age   20   (null))
         第二次结果:(<RACTuple: 0x7c183bc0> (name,testData)   2   name   testData   (null))

我们可以使用RACTupleUnpack把tuple转换成dictionary的键值关系
RACTupleUnpack(NSString *key, NSString *value) = x;
上面的写法和下面的得到的结果是一样的

NSString *key = x[0];
NSString *value = x[1];

RACSequence

简介

RAC中快速遍历Array和Dictionary的类

用途
  • 遍历数组字典
    NSArray *testArr = @[@"1",@"2",@"3",@"4"];

    [testArr.rac_sequence.signal subscribeNext:^(id x) {
        NSLog(@"%@",x); //<RACTuple: 0x796386c0> ( number,1 )
    }];

    NSDictionary *testDic = @{@"name":@"testData",@"age":@(20)};

    [testDic.rac_sequence.signal subscribeNext:^(id x) {
        NSLog(@"%@",x); 
#if 0
        RACTupleUnpack(NSString *key, NSString *value) = x;
        NSLog(@"%@,%@",key,value);
#else
        NSString *key = x[0];
        NSString *value = x[1];
        NSLog(@"%@,%@",key,value);
#endif
    }];
  • 字典转模型
    //OC写法
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"flags.plist" ofType:nil];

    NSArray *dictArr = [NSArray arrayWithContentsOfFile:filePath];

    NSMutableArray *items = [NSMutableArray array];

    for (NSDictionary *dict in dictArr) {
        FlagItem *item = [FlagItem flagWithDict:dict];
        [items addObject:item];
    }

    //RAC写法
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"flags.plist" ofType:nil];

    NSArray *dictArr = [NSArray arrayWithContentsOfFile:filePath];

    NSMutableArray *flags = [NSMutableArray array];

    _flags = flags;

    // rac_sequence注意点:调用subscribeNext,并不会马上执行nextBlock,而是会等一会。
    [dictArr.rac_sequence.signal subscribeNext:^(id x) {
        // 运用RAC遍历字典,x:字典

        FlagItem *item = [FlagItem flagWithDict:x];

        [flags addObject:item];

    }];

    NSLog(@"%@",  NSStringFromCGRect([UIScreen mainScreen].bounds));


    //RAC高级写法:
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"flags.plist" ofType:nil];

    NSArray *dictArr = [NSArray arrayWithContentsOfFile:filePath];
    // map:映射的意思,目的:把原始值value映射成一个新值
    // array: 把集合转换成数组
    // 底层实现:当信号被订阅,会遍历集合中的原始值,映射成新值,并且保存到新的数组里。
    NSArray *flags = [[dictArr.rac_sequence map:^id(id value) {

        return [FlagItem flagWithDict:value];

    }] array];

rac_signalForSelector

简介

可以代替代理

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
    UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"Message" message:@"You will push to the second ViewController,right?" delegate:self cancelButtonTitle:@"cacel" otherButtonTitles:@"okay", nil];
#pragma clang diagnostic pop
    [alert show];

    [[self rac_signalForSelector:@selector(alertView:clickedButtonAtIndex:) fromProtocol:@protocol(UIAlertViewDelegate)] subscribeNext:^(RACTuple *x) {
        NSLog(@"%@",x);
        if ([x[1] boolValue]) {
            [self pushToSecViewController];
        }
    }];


//AlertView的简化写法
    [[alertView rac_buttonClickedSignal] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    self.textField.delegate = self;

    [[self rac_signalForSelector:@selector(textFieldDidBeginEditing:) fromProtocol:@protocol(UITextFieldDelegate)] subscribeNext:^(id x) {
        NSLog(@"开始编辑");
    }];

    [[self rac_signalForSelector:@selector(textFieldDidEndEditing:) fromProtocol:@protocol(UITextFieldDelegate)]
     subscribeNext:^(id x) {
        NSLog(@"结束编辑");
     }];
跟RACSubject区别

RACSubject可以传值
rac_signalForSelector: fromProtocol: 不可以传值

代替KVO

rac_valuesAndChangesForKeyPath

    // 把监听view的center属性改变转换成信号,只要值改变就会发送信号
    // observer:可以传入nil
    [[view rac_valuesAndChangesForKeyPath:@"center" options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) {

        NSLog(@"%@",x);

    }];

代替通知

rac_addObserverForName

    // 把监听到的通知转换信号
    [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(id x) {
        NSLog(@"键盘弹出");
    }];

处理当界面有多次请求时,需要都获取到数据才能进行下一步

rac_liftSelector:withSignalsFromArray:Signals
当传入的Signals(信号数组),每一个signal都至少sendNext过一次,就会去触发第一个selector参数的方法

//处理多个请求,都返回结果的时候,统一做处理.
    RACSignal *signal1 = [RACSignal createSignal:^RACDisposable *(id<racsubscriber> subscriber) {

        // 发送请求1
        [subscriber sendNext:@"发送请求1"];
        return nil;
    }];

    RACSignal *signal2 = [RACSignal createSignal:^RACDisposable *(id<racsubscriber> subscriber) {
        // 发送请求2
        [subscriber sendNext:@"发送请求2"];
        return nil;
    }];

    // Selector调用:当所有信号都发送数据的时候调用
    // 数组存放信号
    // Selector注意点:参数根据数组元素决定
    // Selector方法参数类型,就是信号传递出来数据
    [self rac_liftSelector:@selector(doingSthWithData1: data2:) withSignalsFromArray:@[signal1,signal2]];

RAC常见宏

RAC(target, keyPath)
  • 用法
    用于给某个对象的某个属性绑定。

  • 例子

// 只要文本框文字改变,就会修改label的文字
RAC(self.labelView,text) = _textField.rac_textSignal;
RACOberve(self, name)
  • 用法
    监听某个对象的某个属性,返回的是信号。
  • 例子
[RACObserve(self.view, center) subscribeNext:^(id x) {
    NSLog(@"%@",x);
}];

先写到这里,回头会有补充

本文demo地址
另外还有一个使用RAC+MVVM模拟的登录小demo

参考资料:
学习RAC小记-适合给新手看的RAC用法总结
ReactiveCocoa(FRP)-进阶篇
被误解的 MVC 和被神化的 MVVM
ReactiveCocoa干货

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值