ReactiveCocoa的使用

ReactiveCocoaGitHub上一个开源的函数响应式(Functional Reactive Programming)框架,提供Objective-C ReactiveObjC和Swift ReactiveSwift版本。这里只针对 ReactiveObj 的使用进行讲解,Swift 项目的话推荐使用 RxSwift

为什么要使用 ReactiveCocoa?

MVC 是苹果官方推荐的框架模式,对于早期的 APP 开发来说确实能解决不少问题。但发展到现在,APP 的迭代日积月累,功能也越来越复杂,Controller 就变得十分臃肿,有些老的项目里面三四千行代码的 Controlle r比比皆是,极大减弱了代码可阅读性和可维护性,极其复杂的业务逻辑都揉在 Controller 甚至导致接手项目的童鞋花费大量的时间去改动极少的一部分代码,还容易搞出问题。这时候我们就考虑使用 MVVM 来替代 MVC,遵循高内聚、低耦合的原则进行编程。ReactiveCocoa 对于 MVVM 并不是必需品,但是使用这个框架来实现是为了更好地将 ViewModelController 绑定在一起,以更简洁、更优雅的方式来实现 MVVM

ReactiveCocoa简介

RAC是一个将函数响应式编程范式带入iOS的开源库,其兼具函数式与响应式的特性。它是由Josh Abernathy和Justin Spahr-Summers当初在开发GitHub for Mac过程中创造的,灵感来源于Functional Reactive Programming。所以,这么一个神奇伟大的库,竟然是个副产物!而这个副产物比孕育它的产品出名的多,不得不说很有意思。

那么问题来了,什么是函数响应式编程-简称为FRP 呢?一言以蔽之,FRP是基于异步事件流进行编程的一种编程范式。针对离散事件序列进行有效的封装,利用函数式编程的思想,满足响应式编程的需要。

ReactiveCocoa基本使用

RACStream

RACStream 是一个抽象类,ReactiveCocoa工程中通常使用它的子类 RACSignalRACSequence。作为抽象类本身不提供方法的实现,所有基于流的操作都可以建立在该类之上。

其包含5个需要子类重写的方法:emptyreturn:bind:concat:zipwith:

RACEvent

上面提到,响应式编程可以将变化的值通过数据流进行传播。为此,RAC 中定义了一个事件的概念,即:RACEvent

事件分三种类型:Next类型,Completed 类型和 Error 类型。其中 Next 类型和 Error 类型事件内部可以承载数据,而 Completed 类型并不。

RACSignal

这是 RAC 中最基本的一个概念,中文名叫做“信号”,搞懂了这个类,就可以用 RAC 去玩耍了。

信号代表的是一个随时间而改变的值流。作为一个流,可以将不断变化的值(或者说数据)向外进行传播。想获取一个信号中的数据,需要订阅它。什么是订阅呢?和订阅博客,订报纸,订牛奶一个意思。但前提是这个信号是存在的,所以想要订阅必先创建。反过来说,创建了一个信号但是并没有订阅它,也获取不到其内部的数据。(这种情况下 RACSignal 信号根本就不会向外发送数据,下一篇中会详细介绍,暂时忽略)。当一个订阅过程结束时,如有必要去做一些清理工作(当然为了回收资源需要将信号销毁,但 RAC 内部会自动处理,使用者无需关心)。综上,一个信号完整的使用过程应该是创建,订阅,清理。

信号被订阅了之后,可以认为在信号源和订阅者之间建立起了一座桥梁,通过它信号源源不断的向订阅者发送最新数据,直到桥被销毁。但是要注意,这是一条很窄而且承重很差的桥,以至于一次只能通过一条数据。如果将一条数据理解成一个人,那么通俗的说就是只有一个人通过了另一个人才能继续过,而绝不能同时两个人走上桥。

信号向外传播数据的载体就是事件。其中 Next 类型事件可以承载任意类型数据-即id ,甚至可以是 nil 。但一般不用来承载错误类型数据,因为有 Error 类型事件单独做这件事。Completed 类型事件仅作为一个正常完成的标志,不承载任何数据。

信号被订阅了之后,可以发送任意多个 Next 事件(当然可以是0),直到发送了一个 Completed 事件或者一个 Error 事件,这两个事件都标志着结束,区别在于 Completed 事件表示正常结束,而 Error 事件表示因为某种错误而结束。只要两者之一被发送了,整个订阅过程就结束了。

- (void)racCreateSignalDemo {
    RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id  _Nonnull subscriber) {
        NSString *jsonString = @"";
        NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
        NSError *error;
        NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&error];
        if (!error) {
            [subscriber sendNext:dic];
            [subscriber sendCompleted];
        }else {
            [subscriber sendError:error];
        }
        return [RACDisposable disposableWithBlock:^{
            NSLog(@"信号被销毁"); //当信号发送完成或者发送错误
        }];
    }];
    [signal subscribeNext:^(NSDictionary * _Nullable x) {
        NSLog(@"%@", x);
    } error:^(NSError * _Nullable error) {
        NSLog(@"%@", error);
    } completed:^{
        NSLog(@"completed!");
    }];
}
RACSubscriber

订阅者,这是一个协议,只要遵循这个协议就能成为订阅者,用于发送信号。

@protocol RACSubscriber 
@required
- (void)sendNext:(nullable id)value;
- (void)sendError:(nullable NSError *)error;
- (void)sendCompleted;
- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable;
@end
RACDisposable

用于取消订阅或者清理资源,当信号发送完成或发送错误时会自动执行 disposableWithBlock

- (void)racDisposeDemo {
    RACSignal *s = [RACSignal createSignal:^RACDisposable * _Nullable(id  _Nonnull subscriber) {
        [subscriber sendNext:@"hi~"];
        [subscriber sendCompleted];
        return [RACDisposable disposableWithBlock:^{
            NSLog(@"disposable..."); // disposable...
        }];
    }];
    RACDisposable *d = [s subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x); // hi~
    }];
    [d dispose]; //可以主动取消订阅
}
RACScheduler

是对 GCD 的封装,并且支持取消操作,取消操作其实是没有执行回调。

RACObserve

RAC() 可以将 Signal 发出事件的值赋值给某个对象的某个属性,其参数为对象名和属性名

RACObserve() 参数为对象名和属性名,新建一个 Signal 并对对象的属性的值进行观察,当值变化时 Signal 会发出事件

比较常见的用法就是 RACObserve(someTarget, someProperty),但是大家了解 RACObserve(target.someTarget, someProperty)RACObserve(target, someTarget.someProperty) 之间的区别么?具体可以看以下代码片段以及执行的结果

self.label = [UILabel new];
self.label.text = @"123";

[RACObserve(self.label, text) subscribeNext:^(id x) {
    NSLog(@"RACObserve(self.label, text) 的方式 %@", x);
}];

[RACObserve(self, label.text) subscribeNext:^(id x) {
    NSLog(@"RACObserve(self, label.text) 的方式 %@", x);
}];

self.label.text = @"1234";
self.label = [UILabel new];
self.label.text = @"12345";

// output

RACObserve(self.label, text) 的方式 123
RACObserve(self, label.text) 的方式 123
RACObserve(self, label.text) 的方式 1234
RACObserve(self.label, text) 的方式 1234
RACObserve(self, label.text) 的方式 (null)
RACObserve(self, label.text) 的方式 12345

以上面代码为例,RACObserve(self.label, text) 其实是监听 self.label 这个对象的 text 属性,所以当这个对象 text 发生变化时,第一个是 block 是能够收到回调的,但是当 self.label 被重新赋值后,原来的 label 无人持有相当于变成了 nil,所以第一个 block 将不再生效。而 RACObserve(self, label.text) 监听的是 self,然后 keyPathlabel.text,所以当 label 或者其 text 发生变化都会触发这个回调。所以区别在于 target 以及 keyPath 的设置。

双向绑定:
假设我们的 UIViewController 里有一个 UITextField 和一个 UILabel, 我们希望在 UITextField 输入时, UILabel 里面同步显示一样的内容。我们通过把 UITextFieldUILabel 绑定到同一个 model ,也就是 UIViewControllername 属性上 来实现这一点。

RAC(self.nameTextfield, text) = RACObserve(self, name);
RAC(self.nameLabel, text) = RACObserve(self, name);
    
[self.nameTextfield.rac_textSignal subscribeNext:^(id x) {
    self.name = x;
}];
RACSubject

RACSubjectRACSignal 的子类,自身可以充当信号,也可以发送信号。

- (void)racSubjectDemo {
    RACSubject *subject = [RACSubject subject];
    [subject subscribeNext:^(NSString * _Nullable x) {
        NSLog(@"%@", x);
    }];
    [subject sendNext:@"hi~"];
}

RACSubject 通常情况是用来替代 block 回调。

@interface ViewController : UIViewController
@property (nonatomic, strong, readonly) RACSubject *successSubject;
@end

@implementation ViewController
@synthesize successSubject = _successSubject;
- (void)viewDidLoad {
    [super viewDidLoad];
    @weakify(self);
    [[RACScheduler mainThreadScheduler] afterDelay:3.0 schedule:^{
        @strongify(self);
        [self.successSubject sendNext:@"success!"];
    }];
}
- (RACSubject *)successSubject {
    if (!_successSubject) {
        _successSubject = [RACSubject subject];
    }
    return _successSubject;
}
@end
ViewController *vc = [ViewController new];
[vc.successSubject subscribeNext:^(id  _Nullable x) {
    NSLog(@"%@", x);
}];
RACReplaySubject

RACSubjectRACReplaySubject 的区别:

RACSubject 需要先订阅再发送数据,这样才能接收到数据。 RACReplaySubject在发送数据之前订阅能接收到数据,在发送数据之后的对象再订阅也可以接收到数据。

- (void)racReplaySubjectDemo {
    RACReplaySubject *replaySubject = [RACReplaySubject subject];
    [replaySubject subscribeNext:^(NSString * _Nullable x) {
        NSLog(@"%@", x); //hi~
    }];
    [replaySubject sendNext:@"hi~"];
    [replaySubject subscribeNext:^(NSString * _Nullable x) {
        NSLog(@"%@", x); //hi~
    }];
}
RACCommand

RACCommand 并不表示数据流,它只是一个继承自 NSObject 的类,但是它却可以用来创建和订阅用于响应某些事件的信号。

@interface RACCommand<__contravariant InputType, __covariant ValueType> : NSObject

@end

它本身并不是一个 RACStream 或者 RACSignal 的子类,而是一个用于管理 RACSignal 的创建与订阅的类。

ReactiveCocoa 中的 FrameworkOverview 部分对 RACCommand 有这样的解释:

A command, represented by the RACCommand class, creates and subscribes to a signal in response to some action. This makes it easy to perform side-effecting work as the user interacts with the app.

在用于与 UIKit 组件进行交互或者执行包含副作用的操作时,RACCommand 能够帮助我们更快的处理并且响应任务,减少编码以及工程的复杂度。

具体使用请参考:RACCommand的使用

RACTuple

你一定为当跨方法传多个值时是选择每个值作为一个参数还是选择传一个字典而纠结过。第一种方法可能会令方法名特别长,第二种方法根本不知道字典里面都有哪些 key。为了避免陷入此尴尬,RAC 定义了一个 RACTuple 类。它可以包含多个对象。酷毙了!

为了简化使用,RAC 额外又定义了两个宏:RACTuplePack(...)RACTupleUnpack(...),分别用来“装包”和“拆包”。

NSString *when = @"2021-01-13";
NSString *where = @"Beijing";
NSString *who = @"Fengxiao";
RACTuple *tuple = RACTuplePack(when, where, who);
RACTupleUnpack(NSString *time, NSString *place, NSString *person) = tuple;
NSLog(@"Three element are : %@, %@, %@",time,place,person);

执行结果:


执行结果
RACSequence
  • 数组
- (void)arrayDemo {
    NSArray *array = @[@"A", @"B", @1, @2];
    [array.rac_sequence.signal subscribeNext:^(id x) {
        NSLog(@"value:%@", x);
    }];
}
  • 字典
- (void)racDictionaryDemo {
    NSDictionary *dict = @{@"name": @"Persona", @"address": @"Tianfu Software Park"};
    [dict.rac_sequence.signal subscribeNext:^(RACTuple *x) {
        RACTupleUnpack(NSString *key, NSString *value) = x;
        NSLog(@"key is: %@, value is: %@",key, value);
    }];
}
RACMulticastConnection

用于当一个信号需要被多次订阅,为了保证创建信号时避免多次调用创建信号中的block,可以使用 RACMulticastConnection 来解决。

- (void)racRACSignalDemo {
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
        NSLog(@"发送数据");
        [subscriber sendNext:@"hello"];
        return nil;
    }];
    [signal subscribeNext:^(id x) {
        NSLog(@"接受数据:%@", x);
    }];
    [signal subscribeNext:^(id x) {
        NSLog(@"接受数据:%@", x);
    }];
}
2021-01-13 14:46:32.438670+0800 001---RACCommand[12454:276098] 发送数据
2021-01-13 14:46:32.438768+0800 001---RACCommand[12454:276098] 接受数据:hello
2021-01-13 14:46:32.439045+0800 001---RACCommand[12454:276098] 发送数据
2021-01-13 14:46:32.439124+0800 001---RACCommand[12454:276098] 接受数据:hello
- (void)racRACMulticastConnectionDemo {
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
        NSLog(@"发送数据");
        [subscriber sendNext:@"hello"];
        return nil;
    }];
    RACMulticastConnection *connect = [signal publish];
    [connect.signal subscribeNext:^(id x) {
        NSLog(@"订阅者1:%@", x);
    }];
    [connect.signal subscribeNext:^(id x) {
        NSLog(@"订阅者2 %@", x);
    }];
    [connect connect];
}
2021-01-13 14:47:08.712054+0800 001---RACCommand[12487:277535] 发送数据
2021-01-13 14:47:08.712182+0800 001---RACCommand[12487:277535] 订阅者1:hello
2021-01-13 14:47:08.712280+0800 001---RACCommand[12487:277535] 订阅者2 hello
数据绑定
RAC()

用于和某个对象的属性绑定

- (void)racTextFieldDemo {
    RAC(self, name) = self.textField.rac_textSignal;
    [self.textField.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
        NSLog(@"%@", self.name);
    }];
}
RACChannelTo()

双向绑定

- (void)racRACChannelToDemo {
    RACChannelTo(self, name) = RACChannelTo(self.textField, text);
    self.textField.text = @"1";
    NSLog(@"%@", self.name); // 1
    self.name = @"2";
    NSLog(@"%@", self.textField.text); // 2
}
定时
interval: 定时器
- (void)racTimerDemo {
    __block NSInteger second = 0.f;
    NSInteger duration = 30.f;
    [[[[RACSignal interval:1.0 onScheduler:RACScheduler.mainThreadScheduler] take:duration] takeUntil:self.rac_willDeallocSignal] subscribeNext:^(NSDate * _Nullable x) {
        second++;
        NSLog(@"%lds", second);
    } completed:^{
        NSLog(@"completed!");
    }];
}
delay: 延迟接收
- (void)racDelayDemo {
    RACSignal *s = [[RACSignal createSignal:^RACDisposable * _Nullable(id  _Nonnull subscriber) {
        [subscriber sendNext:@"delay"];
        [subscriber sendCompleted];
        return nil;
    }] delay:5.0];
    [s subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
}
timeout: 超时
- (void)racTimeOutDemo {
    RACSignal *s = [[RACSignal createSignal:^RACDisposable * _Nullable(id  _Nonnull subscriber) {
          //TODO
        return nil;
    }] timeout:5.0 onScheduler:RACScheduler.currentScheduler];
    [s subscribeNext:^(id  _Nullable x) {
        NSLog(@"result: %@", x);
    } error:^(NSError * _Nullable error) {
        NSLog(@"error: %@", error); //error: Error Domain=RACSignalErrorDomain Code=1 "(null)"
    }];
}
基本使用
UIGestureRecognizer
- (void)racTapGestureDemo {
    [self.tapGesture.rac_gestureSignal subscribeNext:^(__kindof UIGestureRecognizer * _Nullable x) {
        NSLog(@"%@", x); // ; target= <(action=sendNext:, target=)>>
    }];
}
delegate
 #pragma mark -遵循
- (void)racDelegateDemo {
    self.nameTextField.delegate = self;
    [[self rac_signalForSelector:@selector(textFieldDidBeginEditing:) fromProtocol:@protocol(UITextFieldDelegate)] subscribeNext:^(RACTuple * _Nullable x) {
        NSLog(@“%@“, x);
    }];
}
NSNotificationCenter
- (void)racNotificationDemo {
    [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIApplicationDidEnterBackgroundNotification object:nil] subscribeNext:^(NSNotification * _Nullable x) {
        NSLog(@"%@", x); //NSConcreteNotification 0x280891230 {name = UIApplicationDidEnterBackgroundNotification; object = }
    }];
}
KVO
- (void)racRACObserveDemo {
    [RACObserve(self, nam
e) subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
}
UI交互
UIButton
- (void)racButtonDemo {
    [[self.button rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
        NSLog(@"%@", x); //>
    }];
}
UIDatePicker
- (void)racDatePickerDemo {
    [[self.datePicker rac_newDateChannelWithNilValue:nil] subscribeNext:^(NSDate * _Nullable x) {
        NSLog(@"%@", x); //Tue Nov 19 16:25:00 2020
    }];
}
UIImagePickerController
- (void)racImagePickerGestureDemo {
    [[self.imagePicker rac_imageSelectedSignal] subscribeNext:^(NSDictionary * _Nullable x) {
        NSLog(@"%@", x);
        /*
         UIImagePickerControllerCropRect = "NSRect: {{0, 0}, {1024, 1024}}";
         UIImagePickerControllerEditedImage = "";
         UIImagePickerControllerImageURL = "file:///private/var/mobile/Containers/Data/Application/BF742343-BEF3-4BE2-BE73-4C6103E07B68/tmp/55783D0E-CE19-4FF6-B1F7-EA754C6C09A4.jpeg";
         UIImagePickerControllerMediaType = "public.image";
         UIImagePickerControllerOriginalImage = "";
         UIImagePickerControllerReferenceURL = "assets-library://asset/asset.JPG?id=C33A82F5-AFB0-4521-8448-9ADD718601B3&ext=JPG";
         */
    }];
}
UISegmentedControl
- (void)racSegmentDemo {
    [[self.segment rac_newSelectedSegmentIndexChannelWithNilValue:nil] subscribeNext:^(NSNumber * _Nullable x) {
        NSLog(@"%@", x);
    }];
}
UISlider
- (void)racSliderDemo {
    [[self.slider rac_newValueChannelWithNilValue:0] subscribeNext:^(NSNumber * _Nullable x) {
        NSLog(@"%@", x);
    }];
}
UISwitch
- (void)racSwitchDemo {
    [[self.aswitch rac_newOnChannel] subscribeNext:^(NSNumber * _Nullable x) {
        NSLog(@"%@", x);
    }];
}
UIStepper
- (void)racStepperDemo {
    [[self.stepper rac_newValueChannelWithNilValue:nil] subscribeNext:^(NSNumber * _Nullable x) {
        NSLog(@"%@", x);
    }];
}
UITextField(UITextView跟它类似)
- (void)racTextFieldDemo {
    // 监听textField文本的变化
    [self.textField.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
        NSLog(@"%@", x);
    }];
    // 双向绑定
    RACChannelTo(self.label, text) = self.textField.rac_newTextChannel;
}

ReactiveCocoa组合操作符

映射
flattenMap:

flattenMap 作用:把原信号的内容映射成一个新信号,并 return 返回给一个 RACStream 类型数据。实际上是根据前一个信号传递进来的参数重新建立了一个信号,这个参数,可能会在创建信号的时候用到,也有可能用不到。

- (void)racFlattenMapDemo {
    [[self.accountTF.rac_textSignal flattenMap:^__kindof RACSignal * _Nullable(NSString * _Nullable value) {
        return [RACReturnSignal return:[NSString stringWithFormat:@"自定义了返回信号:%@",value]];
    }]
     subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@",x);
    }];
}

依次输入5个1打印如下:


返回结果
map:

map 作用:是将原信号的值自定义为新的值,不需要再返回 RACStream 类型,value 为源信号的内容,将 value 处理好的内容直接返回即可。map 方法将会创建一个一模一样的信号,只修改其 value

- (void)racMapDemo {
    [[self.accountTF.rac_textSignal map:^id _Nullable(NSString * _Nullable value) {
        return [NSString stringWithFormat:@"自定义了返回信号:%@",value];
    }]subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@",x);
    }];
}

依次输入12345打印如下:

返回结果

总结一下,同样作为映射命令,在实际开发过程中,如果使用 map 命令,则 block 代码块中 return 的是对象类型;而 flattenMap 命令 block 代码块中 return 的是一个新的信号。

组合
concat:

作用:按 concat: 的顺序来拼接多个信号,并按照这个顺序来接收信号源的值。多个任务串行的方式来执行,并依次返回结果。
场景: 有两个接口请求,第二个接口的请求参数依赖第一个接口请求返回的数据。
注意:前一个信号必须要完成了,也就是要调用 sendCompleted ,那么后一个信号才能执行;如果前一个信号 sendError: 的话,后面的信号都无法执行。

- (void)racConcatDemo {
    RACSubject *subjectA = [RACSubject subject];
    RACSubject *subjectB = [RACReplaySubject subject];
    NSMutableArray *array = [NSMutableArray array];
    [[subjectA concat:subjectB] subscribeNext:^(id  _Nullable x) {
        [array addObject:x]; // 即使subjectB先sendNext值,但是根据concat的顺序,这里的订阅也是先收到subjectB的值
    }];
    [subjectB sendNext:@"b"];
    [subjectA sendNext:@"a"];
    [subjectA sendCompleted];
    NSLog(@"%@", array); // (a, b)
}
- (void)racConcatDemo {
    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {
        [[RACScheduler scheduler] afterDelay:5.0 schedule:^{ // → ①
            [subscriber sendNext:@"A"]; // → ②
            [subscriber sendCompleted];
        }];
        return nil;
    }];
    RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {
        [[RACScheduler scheduler] afterDelay:3.0 schedule:^{ // // → ③
            [subscriber sendNext:@"B"]; // → ④
            [subscriber sendCompleted];
        }];
        return nil;
    }];
    RACSignal *signalC = [RACSignal createSignal:^RACDisposable *(id subscriber) {
        [subscriber sendNext:@"C"]; // → ⑤
        [subscriber sendCompleted];
        return nil;
    }];
    //第一个信号必须发送完成,第二个信号才会被激活. signalA发送完成后,也就是说signalA的block内必须执行[subscriber sendCompleted],才会执行signalB的sendNext发送数据
    [[[signalA concat:signalB] concat:signalC] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
}
then:

作用:用于连接两个信号(或者是多个信号),只有前一个信号完成了,才会连接到后一个信号。按顺序来监听信号,而且只能获得最后一个信号的值。
场景:与 concat 很相似。
注意:前一个信号必须要完成了,也就是要调用 sendCompleted,那么后一个信号才能执行;如果前一个信号 sendError: 的话,后面的信号都无法执行。

- (void)racThenDemo {
    RACSignal *signalA = [RACSignal createSignal:^RACDisposable * _Nullable(id  _Nonnull subscriber) {
        [subscriber sendNext:@1]; // → ①
        [subscriber sendCompleted]; // → ②
        return nil;
    }];
    RACSignal *signalB = [RACSignal createSignal:^RACDisposable * _Nullable(id  _Nonnull subscriber) {
        [subscriber sendNext:@2]; // → ④
        [subscriber sendCompleted]; // → ⑥
        return nil;
    }];
    [[signalA then:^RACSignal * _Nonnull{
        return signalB; // → ③
    }] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x); // → ⑤
    }];
}
merge:

作用:把多个信号合并为一个信号,任何一个信号发送新的数据就会触发 subscribeNext。接收的顺序是信号的发送顺序。
场景:多个并发请求,对顺序没有要求,谁先返回结果,订阅者就先收到谁发送的值。
注意:一旦某一个信号触发 sendError,那么就会终止接收其他信号源再发送的数据。比如 A-B-C-DB 触发了错误,将不再接收 B-C-D 发送的值。

- (void)racMergeDemo {
    RACSubject *subjectA = [RACSubject subject];
    RACSubject *subjectB = [RACSubject subject];
    RACSubject *subjectC = [RACSubject subject];
    RACSignal *single = [[subjectA merge:subjectB] merge:subjectC];
    [single subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x); // 第一次接收到:A,第二次接收到:B,第三次接收到:C
    }];
    [subjectA sendNext:@"A"];
    [subjectC sendNext:@"C"];
    [subjectB sendNext:@"B"];
}
// A C B
- (void)racMergeDemo {
    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {
        [[RACScheduler scheduler] afterDelay:5.0 schedule:^{ // → ①
            [subscriber sendNext:@"A"]; // → ④
            [subscriber sendCompleted];
        }];
        return nil;
    }];
    RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {
        [[RACScheduler scheduler] afterDelay:3.0 schedule:^{ // → ②
            [subscriber sendNext:@"B"]; // → ③
            [subscriber sendCompleted];
        }];
        return nil;
    }];
    // 合并信号, 任何一个信号发送数据,都能监听到
    [[signalA merge:signalB] subscribeNext:^(id x) {
        NSLog(@"%@", x); // 第一次接收到B,第二次接收到A
    }];
}
zip:

作用:将两个 signal 压缩成一个 signal,并且两个 signal 都要发送 value,订阅者才会接收到数据,接收到的数据是一个元祖对象。
场景:两个任务并行执行,订阅者拿到最终的执行结果。 zipWith: 的使用最好不要搞太复杂,通常压缩两个异步任务即可。
注意:❗这里有个特殊的现象:无论是 [signalA zipWith:signalB] 还是 [signalB zipWith: signalA],根据先发送数据的信号源的 sendNext 次数,去匹配后一个信号源 sendNext。假设 signalA 先发送了 2 次,即使 signalB 再发送了 3 次,最终订阅者也只会收到 2 次,多出来的数据将会被过滤掉,执行顺序是A1A2B1subscribeNextB2subscribeNextB3 →无法匹配 signalA 数据→终止。

- (void)racZipWithDemo {
    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {
        [[RACScheduler scheduler] afterDelay:2.0 schedule:^{
            [subscriber sendNext:@"A-1"]; // → ②
        }];
        [[RACScheduler scheduler] afterDelay:3.0 schedule:^{
            [subscriber sendNext:@"A-2"]; // → ④
        }];
        return nil;
    }];
    RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {
        [[RACScheduler scheduler] afterDelay:1.0 schedule:^{
            [subscriber sendNext:@"B-1"]; // → ①
        }];
        [[RACScheduler scheduler] afterDelay:4.0 schedule:^{
            [subscriber sendNext:@"B-2"]; // → ⑤
        }];
        return nil;
    }];
    [[RACSignal zip:@[signalA, signalB]] subscribeNext:^(id x) {
        NSLog(@"%@", x); // → ③  ("A-1","B-1")   // → ⑥ ("A-2","B-2")
    }];
}
zipWith:
- (void)racZipWithDemo {
    RACSubject *subjectA = [RACSubject subject];
    RACSubject *subjectB = [RACSubject subject];
    RACSignal *single = [subjectA zipWith:subjectB];
    [single subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
    [subjectA sendNext:@"A"];
    [subjectB sendNext:@"B"];
    // (A, B)
}
- (void)racZipWithDemo {
    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {
        [[RACScheduler scheduler] afterDelay:2.0 schedule:^{ // → ①
            [subscriber sendNext:@"A-1"]; // → ③
            [subscriber sendNext:@"A-2"]; // → ④
            [subscriber sendNext:@"A-3"]; // → ⑤
            [subscriber sendCompleted];
        }];
        return nil;
    }];
    RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {
        [[RACScheduler scheduler] afterDelay:3.0 schedule:^{ // → ②
            [subscriber sendNext:@"B-1"]; // → ⑥
            [subscriber sendNext:@"B-2"]; // → ⑧
            [subscriber sendCompleted];
        }];
        return nil;
    }];
    [[signalA zipWith:signalB] subscribeNext:^(id x) {
        NSLog(@"%@", x); // → ⑦  ("A-1","B-1")   // → ⑨ ("A-2","B-2")
    }];
}
combineLatest:

作用:将数组中的多个 signal 打包成一个 signal,并且每个 signal 都要发送数据,才会接收到数据。
场景:监听手机号 textField 和验证码 textField 的输入情况
注意
subjectA 发送了新的值,这时候订阅者是不会接收到数据的,必须 subjectB 也发送了数据,那么订阅者才会接收到数据。
subjectA发送1subjectB 发送2,这时候订阅者接收到的最新数据是(1, 2);然后subjectA 发送 3,那么 subjectA 更新了数据,这时候订阅者接收到的数据就是(3, 2)

- (void)racCombineLatestDemo {
    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {
        [[RACScheduler scheduler] afterDelay:2 schedule:^{
            [subscriber sendNext:@"A1"]; // -> ②
        }];
        [[RACScheduler scheduler] afterDelay:4 schedule:^{
            [subscriber sendNext:@"A2"]; // -> ④
        }];
        return nil;
    }];
    RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {
        [[RACScheduler scheduler] afterDelay:1 schedule:^{
            [subscriber sendNext:@"B1"]; // -> ①
        }];
        [[RACScheduler scheduler] afterDelay:3 schedule:^{
            [subscriber sendNext:@"B2"]; // -> ③
        }];
        [[RACScheduler scheduler] afterDelay:5 schedule:^{
            [subscriber sendNext:@"B3"]; // -> ⑤
        }];
        return nil;
    }];
    RACSignal *combineSianal = [RACSignal combineLatest:@[signalA, signalB]];
    [combineSianal subscribeNext:^(id x) {
        NSLog(@"combineLatest:%@", x); //(A1, B1),(A1, B2),(A2, B2),(A2, B3)
    }];
}
combineLatestWith:
- (void)racCombineLatestWithDemo {
    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {
        [[RACScheduler scheduler] afterDelay:2 schedule:^{
            [subscriber sendNext:@"A1"]; // -> ②
        }];
        [[RACScheduler scheduler] afterDelay:4 schedule:^{
            [subscriber sendNext:@"A2"]; // -> ④
        }];
        return nil;
    }];
    RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {
        [[RACScheduler scheduler] afterDelay:1 schedule:^{
            [subscriber sendNext:@"B1"]; // -> ①
        }];
        [[RACScheduler scheduler] afterDelay:3 schedule:^{
            [subscriber sendNext:@"B2"]; // -> ③
        }];
        [[RACScheduler scheduler] afterDelay:5 schedule:^{
            [subscriber sendNext:@"B3"]; // -> ⑤
        }];
        return nil;
    }];
    RACSignal *combineSianal = [signalA combineLatestWith:signalB];
    [combineSianal subscribeNext:^(id x) {
        NSLog(@"combineLatest:%@", x); //(A1, B1),(A1, B2),(A2, B2),(A2, B3)
    }];
}
reduce:

combineLatest: reduce:
还有 zip: reduce: 以及其他的,这里以 combineLatest: reduce: 为例:
将打包的多个 signalvalue,重新聚合为一个新的 value

- (void)racCombineLatestReduceDemo {
    RACSignal *s1 = RACObserve(self.phoneTextfield, text);
    RACSignal *s2 = RACObserve(self.codeTextfield, text);
    RACSignal *validSignal = [RACSignal combineLatest:@[s1, s2] reduce:^(NSString *phone, NSString *code){
        return @(phone.length == 11 && code.length == 6);
    }];
}
过滤
filter:

过滤掉无法满足条件的。

- (void)racFilterDemo {
    [[[self.textField rac_textSignal] filter:^BOOL(NSString * _Nullable value) {
        return value.length < 10;
    }] subscribeNext:^(NSString * _Nullable x) {
        NSLog(@"%@", x); // 只能接收到字符串长度小于10的value
    }];
}
ignore:

忽略掉特定的值。

- (void)racIgnoreDemo {
    [[[self.textField rac_textSignal] ignore:@"."] subscribeNext:^(NSString * _Nullable x) {
        NSLog(@"%@", x); // 这里文本输入框里的内容和条件进行比较
    }];
}
distinctUntilChanged:

当前的 value 与上次相比有变化才会收到 value,否则会被忽略掉。
应用场景:针对刷新 UI,如果数据没有发生改变的话,就没必要浪费资源去刷新UI

- (void)racDistinctUntilChangedDemo {
    RACSubject *subject = [RACSubject subject];
    [[subject distinctUntilChanged] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x); // 1, 2
    }];
    [subject sendNext:@1];
    [subject sendNext:@1]; // 第二次发送的数据对比上次,是没有发生改变的,这种就会被忽略掉
    [subject sendNext:@2];
}
take:

一共取 N 次信号发送的 value

- (void)racTakeDemo {
    RACSubject *subject = [RACSubject subject];
    [[subject take:2] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x); // 1, 2
    }];
    [subject sendNext:@1];
    [subject sendNext:@2];
    [subject sendNext:@3]; // 因为只会取两次,所以第三次发送的数据旧被忽略掉了
    [subject sendCompleted];
}
takeLast:

取结束前的 N 次信号发送的 value,必须要调用 sendCompleted

- (void)racTakeLastDemo {
    RACSubject *subject = [RACSubject subject];
    [[subject takeLast:2] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x); // 2, 3
    }];
    [subject sendNext:@1]; // 从第二次开始取,所以第一次发送的数据被忽略掉了
    [subject sendNext:@2];
    [subject sendNext:@3];
    [subject sendCompleted];
}
takeUntil:

直到 subjectB 发送后,subjectA 便无法再接收到数据。

- (void)racTakeUntilDemo {
    RACSubject *subjectA = [RACSubject subject];
    RACSubject *subjectB = [RACSubject subject];
    [[subjectA takeUntil:subjectB] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x); // 1
    }];
    [subjectA sendNext:@1];
    [subjectB sendNext:@2];
    [subjectA sendNext:@3];
}
switchToLatest:

signalOfSignals,取信号中的信号。

- (void)racSwitchToLatestDemo {
    RACSubject *subjectA = [RACSubject subject];
    RACSubject *subjectB = [RACSubject subject];
    [[subjectA switchToLatest] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
    [subjectA sendNext:subjectB];
    [subjectB sendNext:@"signal in signal"];
}

也可以用于 RACCommand 的订阅

- (void)racSwitchToLatestDemo {
    [self.command.executionSignals.switchToLatest subscribeNext:^(id  _Nullable x) {
        //TODO
        NSLog(@"%@", x);
    }];
    [self.command.errors subscribeNext:^(NSError * _Nullable x) {
        //TODO
    }];
    [self.command execute:nil];
}
skip:

跳过 N 次接收 value

- (void)racSkipDemo {
    RACSubject *subject = [RACSubject subject];
    [[subject skip:2] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x); // 3, 4
    }];
    [subject sendNext:@1];
    [subject sendNext:@2];
    [subject sendNext:@3];
    [subject sendNext:@4];
}
重复操作
retry:

只要失败sendError:,就会循环执行 block 内的代码,直到成功sendNext:为止;

- (void)racRetryDemo {
    __block int i = 0;
    RACSignal *s = [RACSignal createSignal:^RACDisposable * _Nullable(id  _Nonnull subscriber) {
        if (i > 3) {
            [subscriber sendNext:[NSString stringWithFormat:@"i【%d】> 3", i]];
        }else {
            NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:1 userInfo:@{NSLocalizedDescriptionKey: @"变量小于等于3"}];
            [subscriber sendError:error];
        }
        i++;
        return nil;
    }];
    [[s retry] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    } error:^(NSError * _Nullable error) {
        NSLog(@"%@", error);
    }];
}
replay:

如果不使用 replay 的话,多次订阅会多次执行 signal 内的代码;如果使用了 replay 的话,多次订阅也只会执行一次 signal 内的代码。

- (void)racRelayDemo {
    RACSignal *s = [[RACSignal createSignal:^RACDisposable * _Nullable(id  _Nonnull subscriber) {
        [subscriber sendNext:@"hi~"];
        [subscriber sendCompleted];
        return nil;
    }] replay];
    [s subscribeNext:^(id  _Nullable x) {
        NSLog(@"第一次:%@", x);
    }];
    [s subscribeNext:^(id  _Nullable x) {
        NSLog(@"第二次:%@", x);
    }];
}
throttle:

当某个 signal 发送比较频繁时,可以使用 throttle 达到节流的目的,throttle 代表多少秒之后才会订阅。

- (void)racThrottleDemo {
    RACSubject *subject = [RACSubject subject];
    [[subject throttle:3] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
    int i = 1000;
    while (i) {
        [subject sendNext:@(i)];
        i--;
    }
    dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC));
    dispatch_after(delayTime, dispatch_get_main_queue(), ^{
        [subject sendNext:@"hi~"];
    });
}
其他
doNext:和doError:

它们将会在订阅收到数据之前被调用,通常在订阅接收到数据之前,doNext:doError: 可以先拿到数据做一些操作,但是并不会影响订阅原本要接收的数据。

- (RACCommand *)problemTypeCommand {
    if (!_problemTypeCommand) {
        @weakify(self);
        _problemTypeCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(NSNumber *input) {
            @strongify(self);
            self.problemTypeTemp = nil;
            return [[[[Request fetchProblemTypesSecondStepWithCourseId:self.courseId parentId:input] collect] doNext:^(NSArray *x) {
                  @strongify(self);
                self.problemTypeTemp = [NSArray arrayWithArray:x];
            }] doError:^(NSError * _Nonnull error) {
                  @strongify(self);
                self.problemTypeTemp = nil;
            }];
        }];
    }
    return _problemTypeCommand;
}
collect

将信号发送的数据收集起来,订阅将会收到一个装着这些数据的数组

NSArray *stuInfoArray = @[@{@"name": @"John", @"sex": @"男", @"age": @"18", @"Height": @"180"},
                          @{@"name": @"Tony", @"sex": @"男", @"age": @"16", @"Height": @"172"},
                          @{@"name": @"Linda", @"sex": @"女", @"age": @"13", @"Height": @"138"}];
[[[stuInfoArray.rac_sequence.signal map:^id _Nullable(NSDictionary *value) {
    return [MTLJSONAdapter modelOfClass:Student.class fromJSONDictionary:value error:nil];
}] collect] subscribeNext:^(id  _Nullable x) {
    NSLog(@"%@", x);
}];

你可能感兴趣的:(ReactiveCocoa的使用)