ReactiveCocoa学习总结

初衷

最近刚开发完一款公司的ipad应用,有两个界面的网络请求都达到了十个以上,在控件的创建约束都是由xib来完成的情况下,试图控制器中的代码都达到了1400多行。业务逻辑处理非常混乱,数据和视图之间高度偶耦合。虽然所有功能需求都能实现,但代码的可读性非常差,以后的迭代肯定也非常麻烦。所以趁不忙的时候打算着手研究一下MVVM与ReactiveCocoa。这篇博客会一直记录最近的所学。

基本理解

看了两天的各种博客和官方介绍也只是对其有了一个大概的了解:它是一个iOS开发中常用的各种delegate,KVO,通知 action,netReuquest的集合。说白了也就是有了RAC那些你都不需要了。RAC主要的思想是函数式编程和响应式编程。

主要思想是把运算过程尽量写成一系列嵌套的函数调用

比如我们有这么一个算数

1
1 *(2 + 3)-4

一般的编程思维是

1
2
3
var a = 2 + 3
var b = a * 1
var c = b - 4

而函数编程

1
var result = subtract (multiply (add (2 + 3),1) ,4)

简单来说就是把函数当作另一个函数的参数。

响应式是这样的一个思路:你可以给几乎所有事物创建数据流。数据流无处不在,任何东西都可以成为一个数据流,例如变量、用户输入、属性、缓存、数据结构等等。举个栗子,你可以把你的微博订阅功能想象成跟点击事件一样的数据流,你可以监听这样的数据流,并做出相应的反应。在RAC中信号(RACSignal)可以表现为数据流,我们需要把信号与事物绑定。然后监听这个信号。

比如

1
2
3
a = 2
b = 2
c = a + b

其实就是将a,b的值与c绑定,c监听a,b值的变化,只要a,b发生变化c就随之变化

常见的类
因为现在刚刚学习,接触到的类也不是很多,简单介绍以下目前了解的类。会陆续更新.
1. RACSignal(信号类): Signal是RAC中的核心概念。当数据改变时,信号内部就会收到数据,然后发出。但是默认一个信号是冷信号,当一个信号没有订阅者(Subscriber)时它什么也不干,就像我们的函数,当一个函数写好之后并没有被调用它也什么都不会干。信号可以通过以下三种方式发送事件给订阅者。
1
2
3
[subscriber sendNext:@"接收到的数据"];
[subscriber sendComplete];
[subscriber sendError];


分别是发送,发送完成,发送中的错误,sunscriber可以多次sendNext,但只能有一次sendComplete或sendError。
下面举个例子说明一下
1
2
3
4
5
6
7
8
9
10
11
12
13
//这是一个标准的冷信号,创建好之后发送方法也准备就绪,但并没有被订阅,所以现在处于cold状态。
RACSignal *testSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 每当有订阅者订阅这个信号 才会执行
[subscriber sendNext:@"biubiu"];
[subscriber sendCompleted];
return nil
}];
//现在这个信号被调用了,发送的“biubiu”已经被x所接收。
[testSignal subscribeNext:^(id x) {
//收到信号发送过来的值
NSLog(@"%@", x);
}];


2. RACSubject(可变的信号): 可以连接RAC代码与非RAC代码,可以接收和主动发送信号。看了很多介绍都不推荐使用。目前我只把他当作代理使用了一下。
1
2
3
4
5
6
7
8
9
10
11
12
13
第一步:在第二个界面创建代理信号属性
@property (nonatomic, strong) RACSubject *delegateSignal;
第二步:监听某个值的变化 我在这里监听了这个界面的model。(RACCommadn之后介绍)
[_requestCommand.executionSignals.switchToLatest subscribeNext:^(id model) {
if (self.delegateSignal) {
[self.delegateSignal sendNext:model];
}
}];
第三步:在第一个界面创建第二个界面并设置代理信号然后订阅
self.viewModel.delegateSignal = [RACSubject subject];
[self.viewModel.delegateSignal subscribeNext:^(id model) {
self.model = model;
}];


3.RACCommand(处理事件的类): 监控了事件的整个处理过程。对事件如何处理和其中数据的传递都做了处理。还是直接上代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
第一步:创建命令,命令里必须要返回一个信号,如果不想返回可以返回一个空信号([RACSignal empty]),然后在命令里创建信号,来传输数据,可以是这个命令接收到的也可以是任意的。
RACCommand *command = [[RACCommand alloc]initWithSignalBlock:^RACSignal *(id input) {
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:input];
[subscriber sendCompleted];
return nil;
}];
return signal;
}];
第二步:接收命令里的信号中的数据
[command.executionSignals.switchToLatest subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
第三步:执行这个命令,如果在这里不想传输什么可以传nil
[command execute:@"执行"];
以上三步就是一个完整的command命令流程,这个方法是用来监听命令执行过程中的状态
[command.executing subscribeNext:^(id x) {
if ([x boolValue] == YES) {
NSLog(@"正在执行");
}else{
NSLog(@"尚未执行");
}
}];
log一下,可以看到整个流程
2015-11-12 14:19:50.373 chaoxi[62340:6653950] 尚未执行
2015-11-12 14:19:50.389 chaoxi[62340:6653950] 正在执行
2015-11-12 14:19:50.390 chaoxi[62340:6653950] 执行


4.RACTuple(元组类):用来包装值,下面RACSequence中会用到。
具体介绍可以参考这篇文章

5.RACSequence(集合类):RAC中的NSArray,NSDictionary,可以通过rac_sequence方法使NSArry转换为RACSequence。但是传闻ReactiveCocoa3中将会废弃sequences。所以就简单了解下它的基本操作:
遍历数组:
创建一个数组然后通过rac_sequence操作转换为RACSequence然后订阅信号就会把集合中的所有值遍历
1
2
3
4
5
6
7
8
9
10
11
NSArray *array = @[@"1", @"3", @"5", @"7"];
RACSequence *sequence = [array rac_sequence];
[sequence.signal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
打印结果如下
2015-11-12 15:03:35.875 chaoxi[62641:6697530] 1
2015-11-12 15:03:35.875 chaoxi[62641:6697530] 3
2015-11-12 15:03:35.875 chaoxi[62641:6697530] 5
2015-11-12 15:03:35.876 chaoxi[62641:6697530] 7
遍历字典: 创建一个字典然后通过rac_sequence操作转换为RACSequence遍历,遍历的结果位RACTuple元组,然后把元组解包给参数里面变量赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
NSDictionary *dictonary = @{@"title":@"RAC",@"auther":@"潮汐"};
RACSequence *dSequence = [dictonary rac_sequence];
[dSequence.signal subscribeNext:^(RACTuple *x) {
RACTupleUnpack(NSString *key, NSString *value) = x;
NSLog(@"%@, %@",key,value);
}];
打印结果如下
2015-11-12 15:18:38.108 chaoxi[63381:6753990] title, RAC
2015-11-12 15:18:38.110 chaoxi[63381:6753990] auther, 潮汐

6.其他: 还有很多类似RACScheduler的信号我还没有了解到,这里就不做详细介绍

这是摘自花瓣网工程师博客的一幅图,可以直观的看到各个类之间的关系
t


常用的Category

上面简单介绍了一些基本类,而RAC本身也通过Category为UIKit的很多基本控件添加了很多的Signal,方便我们直接的订阅信号。接下来介绍一些常用的Category。

  1. TextField: rac_textSignal 直接绑定在TextField上的信号,可以实时监听TF上输入的内容。然后进行过滤判断等操作。

    1
    2
    3
    4
    [textField.rac_textSignal subscribeNext:^(id x){
    NSLog(@"%@", x);
    }];
  2. UIButton: rac_command 每当按钮被点击的时候执行这个命令然后返回一个信号,可以根据信号来做一些我们想做的事情。

    1
    2
    3
    4
    button.rac_command = [[RACCommand alloc]initWithSignalBlock:^RACSignal *(id input) {
    // 里面的使用和RACCommand就大同小异了
    }];
  3. UIAlterView: rac_buttonClickedSignal alterView的Signal使我们省去了delegate

    1
    2
    3
    4
    5
    6
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"" message:@"Alert" delegate:nil cancelButtonTitle:@"YES" otherButtonTitles:@"NO", nil];
    [[alertView rac_buttonClickedSignal] subscribeNext:^(int index) {
    // 通过index做出不同点击事件的处理
    }];
    [alertView show];

下面这几个暂时还没用过,记录一下慢慢研究

  1. NSNotificationCenter: rac_addObserverForName

    1
    2
    3
    4
    5
    6
    7
    8
    9
    [[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"NSNotificationCenter" object:nil]subscribeNext:^(NSNotification* x){
    NSLog(@"author:%@", x.userInfo[@"author"]);
    }];
    [[NSNotificationCenter defaultCenter] postNotificationName:@"NSNotificationCenter" object:nil userInfo:@{@"author":@"chaoxi"}];
    log日志
    2015-11-13 11:15:04.538 chaoxi[65891:7195142] auther, 潮汐
  2. NSObject: rac_willDeallocSignal 在一个对象的dealloc触发的时候调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    NSArray *array = @[@"array"];
    [[array rac_willDeallocSignal] subscribeCompleted:^{
    NSLog(@"boo,");
    }];
    array = nil;
    log日志
    2015-11-13 11:15:24.687 chaoxi[65926:7195996] boom!!>


常用的宏

  1. RAC(TARGET, [KEYPATH, [NIL_VALUE]]): 将一个对象的属性和一个signal绑定。

    1
    2
    // 只要文本框一改变,就会给account赋值
    RAC(self.viewModel.account, userName) = _nameTextField.rac_textSignal;
  2. RACObserve(TARGET, KEYPATH): 监听某个target的keypath属性,等效于KVO,并产生一个信号。

    1
    2
    3
    4
    5
    6
    7
    //name只要有变化就给label重新赋值
    RAC(self.Label, text) = RACObserve(self.model, name);
    // 将button与自己的enabled绑定 返回信号
    [RACObserve(self.button, enabled) subscribeNext:^(id x) {
    NSLog(@"%@",x);
    }];
  3. @weakify(Obj) @strongify(Obj): 用在block内部管理对self的引用。一定是成双成对的出现。

    1
    2
    3
    4
    5
    weakify(self)
    [RACObserve(self.button, enabled) subscribeNext:^(id x) {
    @strongify(self)
    }];
  4. RACTuplePack RACTupleUnpack:把数据包装成RACTuple元组类和把元组类解包成数据,前面类的时候提到过


小结

究其本质核心就是signals,也就是bind(绑定),要慢慢转变之前OC的面向对象思维方式,处理相关事物的时候首先想到的就是绑定。基本大多数操作也都是围绕着信号进行的。研究了近一周RAC,到现在也只是理解了基本的概念,能用来简单的替换一些项目中的某些小功能,距离完全用RAC重构项目的路还有很长。接下来还会自己研究一下MVVM,之前做了一个没有RAC的MVVM的小demo,虽然viewController中的数据处理被分离到了viewModel中,但感觉没有RAC的MVVM就像没有花生米的宫保鸡丁,不完美!


一月五号更新,关于信号的处理

  1. map: 一个简单的过滤,可以做一些小处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"test"];
    [subscriber sendCompleted];
    return nil;
    }] map:^id(NSString *value) {
    return [NSString stringWithFormat:@"map - %@", value];
    }]subscribeNext:^(id x) {
    NSLog(@"%@", x);
    }];
    // 打印
    2016-01-05 10:54:54.074 chaoxi[4474:9984804] map - test
  2. filter: 一个加判断的过滤

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"test"];
    [subscriber sendNext:@"filter-test"];
    [subscriber sendCompleted];
    return nil;
    }]filter:^BOOL(NSString *value) {
    return value.length>5;
    }]subscribeNext:^(id x) {
    NSLog(@"%@", x);
    }];
    // 打印出来的只有判断之后的
    2016-01-05 11:02:57.243 chaoxi[4711:9993338] filter-test
  3. delay: 对信号的延时处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"test"];
    [subscriber sendCompleted];
    NSLog(@"--------begin-------");
    return nil;
    }]delay:2]subscribeNext:^(id x) {
    NSLog(@"%@", x);
    }];
    // 延时了两秒
    2016-01-05 11:17:16.635 chaoxi[5568:10011139] --------begin-------
    2016-01-05 11:17:18.636 chaoxi[5568:10011139] test
  4. startWith: 先执行一次

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"test"];
    [subscriber sendCompleted];
    return nil;
    }]startWith:@"startWith"]subscribeNext:^(id x) {
    NSLog(@"%@", x);
    }];
    // 打印
    2016-01-05 11:15:45.905 chaoxi[5363:10007103] startWith
    2016-01-05 11:15:45.906 chaoxi[5363:10007103] test
  5. timeout: 超时,可以设置一段时间后自动报错

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    return nil;
    }]timeout:2 onScheduler:[RACScheduler currentScheduler]]subscribeNext:^(id x) {
    NSLog(@"%@", x);
    }error:^(NSError *error) {
    NSLog(@"%@", error);
    }];
    // 这个信号没有被send所以2秒后直接报错了
    2016-01-05 11:30:38.820 chaoxi[6564:10033941] Error Domain=RACSignalErrorDomain Code=1 "(null)"
  6. take: 只执行前n次信号

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"biu-1"];
    [subscriber sendNext:@"biu-2"];
    [subscriber sendNext:@"biu-3"];
    [subscriber sendNext:@"biu-4"];
    [subscriber sendCompleted];
    return nil;
    }]take:2] subscribeNext:^(id x) {
    NSLog(@"-----------%@", x);
    }];
    // 打印
    2016-01-05 11:33:48.057 chaoxi[6667:10037181] -----------biu-1
    2016-01-05 11:33:48.058 chaoxi[6667:10037181] -----------biu-2
  7. takeLast: 和上面相反 只执行后几次信号

  8. takeUntilBlock: 执行到block中的内容位YES停止

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"biu-1"];
    [subscriber sendNext:@"biu-2"];
    [subscriber sendNext:@"biu-3"];
    [subscriber sendNext:@"biu-4"];
    [subscriber sendCompleted];
    return nil;
    }]takeUntilBlock:^BOOL(id x) {
    return [x isEqualToString:@"biu-2"];
    }] subscribeNext:^(id x) {
    NSLog(@"-----------%@", x);
    }];
    // 只执行到了 biu-1
    2016-01-05 11:35:06.758 chaoxi[6732:10038760] -----------biu-1
  9. takeWhileBlock: 和上面相反 当执行到block内位YES时开始取值

  10. takeUntil: 当给定的signal完成前一直取值 一般配合rac_willDeallocSignal 使用
  11. skip系列 使用方法和take相似,skip跳过前几次;skipUntilBlock:一直跳,跳到block为yes时停止;skipWhileBlock:当位yes的时候开始跳。
  12. distinctUntilChanged 忽略连续两次相同的信号

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"biu-1"];
    [subscriber sendNext:@"biu-1"];
    [subscriber sendNext:@"biu-2"];
    [subscriber sendCompleted];
    return nil;
    }]distinctUntilChanged] subscribeNext:^(id x) {
    NSLog(@"-----------%@", x);
    }];
    // 只执行了一次biu-1,第一次的被忽略
    2016-01-05 11:42:43.285 chaoxi[7046:10047680] -----------biu-1
    2016-01-05 11:42:43.286 chaoxi[7046:10047680] -----------biu-2
  13. throttle: 忽略连续n秒内的值,也叫节流,一般应用在即时搜索中,忽略连续一段时间内的输入

  14. switchToLatest: signal of signal信号的信号。自动切换信号到最后一个
  15. ignore: 忽略指定的值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"biu-1"];
    [subscriber sendNext:@"biu-2"];
    [subscriber sendNext:@"biu-3"];
    [subscriber sendCompleted];
    return nil;
    }]ignore:@"biu-2"] subscribeNext:^(id x) {
    NSLog(@"-----------%@", x);
    }];
    // biu-2 被忽略
    2016-01-05 13:59:04.554 chaoxi[7535:10110841] -----------biu-1
    2016-01-05 13:59:04.555 chaoxi[7535:10110841] -----------biu-3
  16. ignoreValues: 忽略所有值,只取Comletion和Error两个消息

  17. merge: 信号的合并处理, 合并信号后,合并之后的信号一旦被订阅,里面的所有信号都会被订阅。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {
    [subscriber sendNext:@"signalA"];
    return nil;
    }];
    RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {
    [subscriber sendNext:@"signalB"];
    return nil;
    }];
    [[RACSignal merge:@[signalA, signalB]] subscribeNext:^(id x) {
    NSLog(@"我是%@",x);
    }];
    // 打印
    2016-01-05 14:26:52.502 chaoxi[8092:10146925] 我是signalA
    2016-01-05 14:26:52.503 chaoxi[8092:10146925] 我是signalB
  18. combineLatest/zipwith: 合并多个信号,每个合并的signal都有过一次sendNext才能触发合并的信号

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    RACSignal *signalC = [RACSignal createSignal:^RACDisposable *(id subscriber) {
    [subscriber sendNext:@"signalC"];
    [subscriber sendNext:@"signalCC"];
    return nil;
    }];
    RACSignal *signalD = [RACSignal createSignal:^RACDisposable *(id subscriber) {
    [subscriber sendNext:@"signalD"];
    return nil;
    }];
    [[RACSignal combineLatest:@[signalC, signalD]] subscribeNext:^(id x) {
    NSLog(@"%@", x);
    }];
    [[signalC zipWith: signalD] subscribeNext:^(id x) {
    NSLog(@"%@", x);
    }];
    // 打印出来是元组类,使用的话需要解包 combineLatest打印出来的是最新的值
    2016-01-05 14:36:31.428 chaoxi[8326:10158523] <RACTuple: 0x7fa9a9422100> (
    signalCC,
    signalD
    )
    // zipwith打印出来的是第一个值
    2016-01-05 14:38:02.467 chaoxi[8479:10161310] <RACTuple: 0x7fc8c8d061c0> (
    signalC,
    signalD
    )
  19. reduce: 聚合,把合并之后的两个信号的值聚合成一个值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {
    [subscriber sendNext:@"signalA"];
    return nil;
    }];
    RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {
    [subscriber sendNext:@"signalB"];
    return nil;
    }];
    [[RACSignal combineLatest:@[signalA, signalB] reduce:^id(NSString *A, NSString *B){
    return [A stringByAppendingString:B];
    }] subscribeNext:^(id x) {
    NSLog(@"我是%@",x);
    }];
    // 打印
    2016-01-05 14:43:52.968 chaoxi[8652:10168574] 我是signalAsignalB
  20. concat: 有顺序的执行信号,当前一个信号执行完之后才执行下一个

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    RACSignal *signalE = [RACSignal createSignal:^RACDisposable *(id subscriber) {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    [subscriber sendNext:@"signalE"];
    });
    return nil;
    }];
    RACSignal *signalF = [RACSignal createSignal:^RACDisposable *(id subscriber) {
    [subscriber sendNext:@"signalF"];
    return nil;
    }];
    [[signalE concat:signalF] subscribeNext:^(id x) {
    NSLog(@"%@", x);
    }];
    // 注意第一个信号我没有写 sendCompleted 所以最后只执行了第一个信号。
    2016-01-05 14:51:08.711 chaoxi[8939:10178828] signalE

暂时先更到这里


参考链接

http://www.raywenderlich.com/62699/reactivecocoa-tutorial-pt1
http://www.raywenderlich.com/62796/reactivecocoa-tutorial-pt2
https://github.com/AshFurrow/FunctionalReactivePixels
https://github.com/jspahrsummers/GroceryList
http://limboy.me/ios/2013/06/19/frp-reactivecocoa.html