ReactiveCocoa
是一个非常复杂的框架,在正式开始介绍它的核心组件前,我们先来看看它的类图,以便从宏观上了解它的层次结构:
从上面的类图中,我们可以看出,ReactiveCocoa
主要由以下四大核心组件构成:
RACStream
及其子类;RACSubscriber
的实现类及其子类;RACScheduler
及其子类;RACDisposable
及其子类。其中,信号源又是最核心的部分,其他组件都是围绕它运作的。
先来看看RACStream.h文档说明:
/// Maps `block` across the values in the receiver.
///
/// This corresponds to the `Select` method inRx.(Rx见下面备注)
///
/// Returns a new stream with the mapped values.
- (instancetype)map:(id (^)(id value))block;
/// Maps `block` across the values in the receiver and flattens the result.
///
/// Note that operators applied _after_ -flattenMap: behave differently from
/// operators _within_ -flattenMap:. See the Examples section below.
///
/// This corresponds to the `SelectMany` method in Rx.
///
/// block - A block which accepts the values in the receiver and returns a new
/// instance of the receiver's class. Returning `nil` from this block is
/// equivalent to returning an empty signal.
///
/// Examples
///
/// [signal flattenMap:^(id x) {
/// // Logs each time a returned signal completes.
/// return [[RACSignal return:x] logCompleted];
/// }];
///
/// [[signal
/// flattenMap:^(id x) {
/// return [RACSignal return:x];
/// }]
/// // Logs only once, when all of the signals complete.
/// logCompleted];
///
/// Returns a new stream which represents the combined streams resulting from
/// mapping `block`.
- (instancetype)flattenMap:(RACStream * (^)(id value))block;
例子3:
- (void)flattenMap3 {
// flattenMap 主要用于信号中的信号
// 创建信号
RACSubject *signalofSignals = [RACSubject subject];
RACSubject *signal = [RACSubject subject];
// 订阅信号
//方式1
// [signalofSignals subscribeNext:^(id x) {
//
// [x subscribeNext:^(id x) {
// NSLog(@"%@", x);
// }];
// }];
// 方式2
// [signalofSignals.switchToLatest ];
// 方式3
// RACSignal *bignSignal = [signalofSignals flattenMap:^RACStream *(id value) {
//
// //value:就是源信号发送内容
// return value;
// }];
// [bignSignal subscribeNext:^(id x) {
// NSLog(@"%@", x);
// }];
// 方式4--------也是开发中常用的
[[signalofSignals flattenMap:^RACStream *(id value) {
return value;
}] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
// 发送信号
[signalofSignals sendNext:signal];
[signal sendNext:@"123"];
}
//定义2个自定义信号
RACSubject *letters = [RACSubject subject];
RACSubject *numbers = [RACSubject subject];
//组合信号
[[RACSignal combineLatest:@[letters, numbers]
reduce:^(NSString *letter, NSString *number){
//把2个信号的信号值进行字符串拼接
return [letter stringByAppendingString:number];
}] subscribeNext:^(NSString * x) {
NSLog(@"%@", x);
}];
//自己控制发送信号值
[letters sendNext:@"A"];
[letters sendNext:@"B"];
[numbers sendNext:@"1"];//打印B1
[letters sendNext:@"C"];//打印C1
[numbers sendNext:@"2"];//打印C2
可以知道:
1、所有子信号必须都发送信号,才能输出组合信号(combined signal);
2、发送信号可先后发出:子信号可以先后发出信号值(在不同地方调用 [xxx sendNext:xx]);也可理解成事件监听。
3、单个子信号值可重复利用:发送信号值后,该值会一直存在reduce参数表并等待被处理,可以重复被使用,直到有新值覆盖它,可理解成java中的static变量(如上面Letters信号在发送值b后,只要没有新调用[letters sendNext:xx],在reduce:^(NSString *letter, NSString *number)中的letter值将一直会是b),其他子信号同理。
比如上面代码中@"A"没有被输出因为numbers还没有收到一个值。
zipwith一般用于一个界面有多个请求,如:要购买商品,先调用登录接口,接着调用余额查询接口,最后根据查询结果判断余额 >1000才允许购买
- (void)zipWith1 {
//zipWith:把两个信号压缩成一个信号,只有当两个信号同时发出信号内容时,并且把两个信号的内容合并成一个元祖,才会触发压缩流的next事件。
// 创建信号A
RACSubject *signalA = [RACSubjectsubject];
// 创建信号B
RACSubject *signalB = [RACSubjectsubject];
// 压缩成一个信号
// **-zipWith-**: 当一个界面多个请求的时候,要等所有请求完成才更新UI
// 等所有信号都发送内容的时候才会调用
RACSignal *zipSignal = [signalAzipWith:signalB];
[zipSignal subscribeNext:^(id x) {
NSLog(@"%@", x);//所有的值都被包装成了元组
RACTupleUnpack(NSString *stringA, NSString *stringB) = (RACTuple*)x;
NSLog(@"我们是%@%@", stringA, stringB);
}];
// 发送信号交互顺序,元组内元素的顺序不会变,跟发送的顺序无关,而是跟压缩的顺序有关[signalA zipWith:signalB]---先是A后是B
[signalA sendNext:@1];
[signalB sendNext:@2];
[signalB sendNext:@4];
// 只一次打印输出:我们是12
}
- (void)zipWith2 {
//创建信号A
RACSignal *signalA = [RACSignalcreateSignal:^RACDisposable *(id subscriber) {
[subscriber sendNext:@"黄"];
[subscriber sendNext:@"红"];
returnnil;
}];
//创建信号B
RACSignal *signalB = [RACSignalcreateSignal:^RACDisposable *(id subscriber) {
[subscriber sendNext:@"白"];
[subscriber sendNext:@"紫"];
returnnil;
}];
//合流后出来的是压缩包,需要解压才能取到里面的值
[[signalA zipWith:signalB]subscribeNext:^(RACTuple* x) {
//解压缩
RACTupleUnpack(NSString *stringA, NSString *stringB) = x;
NSLog(@"我们是%@%@的", stringA, stringB);
}];
// 先后打印两次:我们是黄白的 \n我们上红紫的
}
当使用诸如RAC(self, outputLabel)或RACObserve(self, name)时,发现写完逗号之后,输入第二个property的时候会出现完全正确的代码提示!这相当神奇。自动代码提示:
探究一下,关键的关键是如下一个宏:
#define keypath(...) \
metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__))(keypath1(__VA_ARGS__))(keypath2(__VA_ARGS__))
这个metamacro_argcount上面说过,是计算可变参数个数,所以metamacro_if_eq的作用就是判断参数个数,如果个数是1就执行后面的keypath1,若不是1就执行keypath2。所以重点说一下keypath2:
#define keypath2(OBJ, PATH) \
(((void)(NO && ((void)OBJ.PATH, NO)), # PATH))
乍一看真挺懵,先化简,由于Objc里面keypath是诸如”outputLabel.text”的字符串,所以这个宏的返回值应该是个字符串,可以简化成:
#define keypath2(OBJ, PATH) (???????, # PATH)
先不管”??????”是啥,这里不得不说C语言中一个不大常见的语法(第一个忽略):
int a = 0, b = 0;
a = 1, b = 2;
int c = (a, b);
这些都是 逗号表达式(具体介绍看这里)的合理用法,第三个最不常用了,c将被b赋值,而a是一个未使用的值,编译器会给出warning。去除warning的方法很简单,强转成void就行了:
(((void)(NO && ((void)OBJ.PATH, NO)), # PATH))
对传入的第一个参数OBJ和第二个正要输入的PATH做了点操作,这也正是为什么输入第二个参数时编辑器会给出正确的代码提示。强转void就像上面说的去除了warning。
//监听命令是否执行完毕,默认会来一次,可以直接跳过,skip表示跳过第一次命令
[[command.executing skip:1] subscribeNext:^(id x) {
// executing表示了当前RACCommand是否在执行 //(3) (8)
if ([x boolValue] ==YES) {
NSLog(@"正在执行");
}else{
NSLog(@"执行完成");
}
}];
通过executing 可以在订阅者中捕获查看 [RACCommand execute] 执行情况,如果Singal没有被关停并且执行了execute,订阅者将返回YES(即1),反之返回NO(即0)
订阅者会不断被回调,不止执行一次。
订阅RACCommand我们可以使用其内部的属性executionSignals返回一个信号,然后对这个信号进行订阅。
[[aCommand executionSignals]
subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
在订阅的block中,我们打印了传递事件x的描述,最后会发现x原来是一个RACSignal,原因是RACCommand中的executionSignals属性是一个包裹着信号的信号,其包裹着的信号就是我们当初在创建RACCommand
时进行构建的信号(即[[RACCommandalloc]initWithSignalBlock在Block中return的信号),所以当我们开始执行RACCommand时,executionSignals信号就会立即发送事件,传递出其包裹的信号,我们可以对这个信号进行订阅:
[[aCommand executionSignals]
subscribeNext:^(RACSignal *x) {
[x subscribeNext:^(id x) {
// Do something...
}];
}];
如果你嫌订阅两个事件麻烦的话,可以使用函数switchToLatest进行转换:
[[[aCommand executionSignals]switchToLatest]
subscribeNext:^(id x) {
// Do something...
}];
这样就比上面少写了一步信号订阅。
如果你想在RACCommand执行时做某些提示操作(弹出等待框,出现转来转去的菊花),并在执行后取消提示,你可以这样写:
[[aCommand executionSignals]
subscribeNext:^(RACSignal *x) {
// 开始提示
[x subscribeNext:^(id x) {
// 关闭提示
// Do something...
}];
}];
在对command进行错误处理的时候,我们不应该使用subscribeError:对command的executionSignals进行错误的订阅,因为executionSignals这个信号是不会发送error事件的,那当command包裹的信号发送error事件时,我们要怎样去订阅它呢?这里用到command的一个属性:errors,我们可以这样来对错误进行订阅:
[aCommand.errors
subscribeNext:^(NSError *x) {
NSLog(@"ERROR! --> %@",x);
}];
部分资料源自互联网,如果发现侵权,请联系博主删除。