概述
ReactiveCocoa
是一个非常复杂的框架,在正式开始介绍它的核心组件前,我们先来看看它的类图,以便从宏观上了解它的层次结构:
从上面的类图中,我们可以看出,ReactiveCocoa
主要由以下四大核心组件构成:
- 信号源:
RACStream
及其子类; - 订阅者:
RACSubscriber
的实现类及其子类; - 调度器:
RACScheduler
及其子类; - 清洁工:
RACDisposable
及其子类。
其中,信号源又是最核心的部分,其他组件都是围绕它运作的。
RACSignal和RACSubject的区别
RACSubject使用步骤
1.创建信号 [RACSubject subject],跟RACSiganl不一样,创建信号时没有block。
2.订阅信号 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
3.发送信号 sendNext:(id)value
RACSubject:底层实现和RACSignal不一样。
1.调用subscribeNext订阅信号,只是把订阅者保存起来,并且订阅者的nextBlock已经赋值了。
2.调用sendNext发送信号,遍历刚刚保存的所有订阅者,一个一个调用订阅者的nextBlock。
RACSubject实例 进行map操作之后 , 发送完毕一定要调用-sendCompleted, 否则会出现内存泄漏;
原因 : 因为RACSubject是热信号, 为了保证未来有事件发生的时候, 订阅者可以收到信息, 所以需要对持有订阅者!
map 和 flattenMap 的区别
FlatternMap适用于:信号发出的是信号
先来看看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;
- - (void)map {
- // Map使用步骤:
- // 1.传入一个block,类型是返回对象,参数是value
- // 2.value就是源信号的内容,直接拿到源信号的内容做处理
- // 3.把处理好的内容,直接返回就好了,不用包装成信号,返回的值,就是映射的值。
- // Map底层实现:
- // 0.Map底层其实是调用flatternMap,Map中block中的返回的值会作为flatternMap中block中的值。
- // 1.当订阅绑定信号,就会生成bindBlock。
- // 3.当源信号发送内容,就会调用bindBlock(value, *stop)
- // 4.调用bindBlock,内部就会调用flattenMap的block
- // 5.flattenMap的block内部会调用Map中的block,把Map中的block返回的内容包装成返回的信号。
- // 5.返回的信号最终会作为bindBlock中的返回信号,当做bindBlock的返回信号。
- // 6.订阅bindBlock的返回信号,就会拿到绑定信号的订阅者,把处理完成的信号内容发送出来。
- // Map作用:把源信号的值映射成一个新的值
- // 创建信号
- RACSubject *subject = [RACSubject subject];
- // 绑定信号
- RACSignal *bindSignal = [subject map:^id(id value) {
- // 返回的类型就是你需要映射的值
- return [NSString stringWithFormat:@"ws:%@", value]; //这里将源信号发送的“123” 前面拼接了ws:
- }];
- // 订阅绑定信号
- [bindSignal subscribeNext:^(id x) {
- NSLog(@"%@", x);
- }];
- // 发送信号
- [subject sendNext:@"123"];
- }
- /*
- FlatternMap和Map的区别
- 1.FlatternMap中的Block返回的是信号。
- 2.Map中的Block返回的是对象。
- 3.Map适用于:信号发出的是值
- 4.FlatternMap适用于:信号发出的是信号
- */
- - (void)flatMap {
- // 创建信号
- RACSubject *subject = [RACSubject subject];
- // 绑定信号
- RACSignal *bindSignal = [subject flattenMap:^RACStream *(id value) {
- // block:只要源信号发送内容就会调用
- // value: 就是源信号发送的内容
- // 返回信号用来包装成修改内容的值
- return [RACReturnSignal return:value];
- }];
- // flattenMap中返回的是什么信号,订阅的就是什么信号(那么,x的值等于value的值,如果我们操纵value的值那么x也会随之而变)
- // 订阅信号
- [bindSignal subscribeNext:^(id x) {
- NSLog(@"%@", x);
- }];
- // 发送数据
- [subject sendNext:@"123"];
- }
- - (void)flattenMap2 {
- // 该例子中,flattenMap 主要用于信号中的信号
- // signalOfsignals 一般用 FlatternMap
- // 创建信号
- RACSubject *signalofSignals = [RACSubject subject];
- RACSubject *signal = [RACSubject subject];
-
- [[signalofSignals flattenMap:^RACStream *(id value) {
- return value;
- }] subscribeNext:^(id x) { // subscribeNext:调用bind 的@autoreleasepool{ } 会将subscribeNext:生成的subscriber 保存到RACSubject中
- NSLog(@"%@", x);
- }];
- // 发送信号
- [signalofSignals sendNext:signal]; // 会执行 80行的 return value;
- [signal sendNext:@"123"]; // 会执行 82行的 NSLog(@"%@", x);
- }
例子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宏自动提示属性
当使用诸如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就行了:
int c = ((void)a, b);
再看上面简化的keypath2宏,返回的就是PATH的字符串字面值了(单#号会将传入值转成字面字符串)
(((void)(NO && ((void)OBJ.PATH, NO)), # PATH))
对传入的第一个参数OBJ和第二个正要输入的PATH做了点操作,这也正是为什么输入第二个参数时编辑器会给出正确的代码提示。强转void就像上面说的去除了warning。
但至于为什么加入与NO做&&,我不太能理解,我测试时其实没有时已经完成了功能,可能是作者为了屏蔽某些隐藏的问题吧。
这个宏的巧妙的地方就在于使得编译器以为我们要输入“点”出来的属性,保证了输入值的合法性(输了不存在的property直接报错的),同时利用了逗号表达式取逗号最后值的语法返回了正确的keypath。
RACCommand之executing
//监听命令是否执行完毕,默认会来一次,可以直接跳过,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)
订阅者会不断被回调,不止执行一次。
RACComand之信号的信号
订阅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);
}];
部分资料源自互联网,如果发现侵权,请联系博主删除。
最后
以上就是奋斗芹菜为你收集整理的iOS之ReactiveCocoa源码及难点分析RACSignal和RACSubject的区别 map 和 flattenMap 的区别RAC宏自动提示属性的全部内容,希望文章能够帮你解决iOS之ReactiveCocoa源码及难点分析RACSignal和RACSubject的区别 map 和 flattenMap 的区别RAC宏自动提示属性所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复