源码分析系列

ReactiveCocoa学习之路

2017-03-08  本文已影响155人  aSnail

ReactiveCocoa

前言

参考博文:

MVVM+RAC英文教程

http://www.jianshu.com/p/87ef6720a096

http://www.cocoachina.com/ios/20150123/10994.html

RAC中涉及到的编程思想:

所以,你可能听说过reactivecocoa被描述为函数响应式编程(**FRP**)框架。
其他平台上也有类似的框架例如java的
****RXJava****_ swift中的__**_ReactiveSwif**


RAC框架的结构(直接略过)

RAC中重要的类

RAC最重要的类是RACSingle(信号类)


所以这里着重介绍一下RACSingle的创建和订阅的实现,从而理解信号的创建以及数据的传递过程

//这里用到一个工厂方法将子类实例返回回去
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
return [RACDynamicSignal createSignal:didSubscribe];
}
//我们再来看看子类`RACDynamicSignal`中的具体实现,其实就是吧传递过来的didSubscribe这个block保存起来
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
RACDynamicSignal *signal = [[self alloc] init];
signal->_didSubscribe = [didSubscribe copy];
return [signal setNameWithFormat:@"+createSignal:"];
}

方法的参数didSubscribe这个block 可以理解为是对信号的描述,
上面只是信号的创建过程,上面提到了默认信号被创建出来以后只是冷信号,也就是didSubscribe这个block只有当RACSingle调用subscribeNext:方法是才会调用,方法里的subscriber有三种方法,也可以理解为可以发送三种信号分别为:nexterrorcompleted,
其中sendNext()是发送我们需要传递的对象,sendError,和sendComplete都会中断信号的订阅.不同的是sendError会传递一个错误值error.一个signal在因error终止或者sendComplete前,可以发送任意数量的next事件

- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock;

下面我们再来看看:subscribeNext:方法的实现过程:

- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
NSCParameterAssert(nextBlock != NULL);
RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock
error:NULL
completed:NULL];
return [self subscribe:o];

}

这里首先创建一个RACSubscriber对象,这个是遵守了RACSubscriber协议的RACSubscriber *类型的对象,这个对象会将传递进来的nextBlock保存起来(也是保存成成员变量),当创建对象时候的didSubscribe中的"subscriber"调用sendNext:的时候nextBlock就会被调用! 有点绕,这里只是介绍了其中大概的工作原理,其中还有RACScheduler的参与,这里就不做介绍了..

RAC为cocoa中的很多类通过分类的形式快速生成信号以及常见的宏使用:

@weakify(self) & @strongify(self): Weak & Strong dance

RACObserve(TARGET, KEYPATH) & RAC(TARGET, ...) :这对宏简直是绝配类似KVC的用法,能够快速地实现对象属性的映射

//e.g.
RAC(self.titleLabel,text) = [RACObserve(self.viewModel, title);

rac_signalForSelector : 执行某一个方法就会生成信号

rac_valuesAndChangesForKeyPath:用于监听某个对象的属性改变。

rac_signalForControlEvents:替代UIControl中的Target模式

rac_addObserverForName用于监听某个通知。

RACTuplePack(...)快速包装成元祖类,成对出现的有RACTupleUnpack(...)

RACTuple *tuple = RACTuplePack(@"xmg",@20);
RACTupleUnpack(NSString *name,NSNumber *age) = tuple;

rac_liftSelector:withSignalsFromArray:Signals:

应用场景: 当界面有多个请求的时候,当所有的请求都回包时才出发某个操作

RACSignal *request1 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 发送请求1 ,并且获取到数据以后:
[subscriber sendNext:@"发送请求1"];
return nil;
}];
RACSignal *request2 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 发送请求2 ,并且获取到数据以后:
[subscriber sendNext:@"发送请求2"];
return nil;
}];
// 使用注意:updateUIWithR1::中的参数和信号所传递过来的数据一一对应
[self rac_liftSelector:@selector(updateUIWithR1:r2:) withSignalsFromArray:@[request1,request2]];

RACSubject类

RACSubject *subject = [RACSubject subject];
[subject subscribeNext:^(id x) {
NSLog(@"第一个订阅者%@",x);
}];
[subject subscribeNext:^(id x) {
NSLog(@"第二个订阅者%@",x);
}];
[subject sendNext:@"1"];

RACReplaySubject与RACSubject区别: RACReplaySubject可以先发送信号,在订阅信号,RACSubject就不可以。如果一个信号每被订阅一次,就需要把之前的值重复发送一遍,使用重复提供信号类。

RAC中的集合类RACSequence

RACSequence是RAC中的集合类,可以实现OC对象与信号中传递值之间的转换,RAC类库中提供了NSArray,NSDictionary等集合类的分类供其转换

//遍历数组
NSArray *numbers = @[@1,@2,@3,@4];
[numbers.rac_sequence.signal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];

//遍历字典
NSDictionary *dict = @{@"name":@"qdaily",@"age":@3};
[dict.rac_sequence.signal subscribeNext:^(RACTuple *x) {
RACTupleUnpack(NSString *key,NSString *value) = x;
NSLog(@"%@ %@",key,value);
}];

RACTupleUnpack将RACTuple进行解包

//使用map方法可以快速进行字典转模型
NSArray *flags = [[dictArr.rac_sequence map:^id(id value) {
return [FlagItem flagWithDict:value];
}] array];

RACCommand:RAC中用于处理事件的类

可以理解为命令类,是对信号和事件的封装

使用场景:监听按钮点击,网络请求
RACCommand使用注意点:

  1. signalBlock必须要返回一个信号,不能传nil.
  2. 如果不想要传递信号,直接创建空的信号[RACSignal empty];
  3. RACCommand中信号如果数据传递完,必须调用[subscriber sendCompleted],这时命令才会执行完毕,否则永远处于执行中。
  4. RACCommand需要被强引用,否则接收不到RACCommand中的信号,因此RACCommand中的信号是延迟发送的。

RACCommand简单使用

RACCommand *requestCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
NSLog(@"请求数据ing...");
//这里即使不需要传递值也要创建空的信号而不能返回nil
// return [RACSignal empty];
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"请求数据"];
// 注意:数据传递完,最好调用sendCompleted,这时命令才执行完毕。
[subscriber sendCompleted]; return nil;
}];
}];
// 强引用命令,不要被销毁,否则接收不到数据,这个command属性一般暴露给外界
_conmmand = command;

//下面通常是外面调用
[command.executionSignals subscribeNext:^(id x) {
[x subscribeNext:^(id x) {
NSLog(@"网络获取的值是:%@",x);
}];
}];

// RACCommand高级用法
// switchToLatest:用于signal of signals,获取signal of signals发出的最新信号,也就是可以直接拿到RACCommand中的信号 [command.executionSignals.switchToLatest subscribeNext:^(id x) {
NSLog(@"%@",x);
}];

//executing 监听命令是否执行完毕,默认会来一次,可以直接跳过,skip表示跳过第一次信号。
[[command.executing skip:1] subscribeNext:^(id x) {
if ([x boolValue] == YES) {
// 正在执行 NSLog(@"正在执行");
}else{
// 执行完成 NSLog(@"执行完成");
}
}];

RACMuticastConnection

RACMulticastConnection:用于当一个信号,被多次订阅时,为了保证创建信号时,避免多次调用创建信号中的block,造成副作用,可以使用这个类处理。

RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"发送请求");
return nil;
}];
// 第一次订阅信号
[signal subscribeNext:^(id x) {
NSLog(@"接收数据");
}];
// 第二次订阅信号
[signal subscribeNext:^(id x) {
NSLog(@"接收数据");
}];
// 3.运行结果:打印两次@"发送请求",也就是每次订阅都会发送一次请求
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"发送请求");
[subscriber sendNext:@1];
return nil;
}];

RACMulticastConnection *connect = [signal publish];

// 注意:订阅信号,也不能激活信号,只是保存订阅者到数组,必须通过连接,当调用连接,就会一次性调用所有订阅者的sendNext:
[connect.signal subscribeNext:^(id x) {
NSLog(@"订阅者一信号");
}];
[connect.signal subscribeNext:^(id x) {
NSLog(@"订阅者二信号");
}];
[connect connect];
//运行结果:只会打印一次@"发送请求"

RAC中信号处理的常用方法

RAC 核心方法绑定bind

对比之前的开发方式是赋值,而用RAC开发,应该把重心放在绑定,也就是在创建一个对象的时候,就绑定好以后想要做的事情,而不是等赋值之后在去做事情。
列如:把数据展示到控件上,之前都是重写控件的setModel方法,用RAC就可以在一开始创建控件的时候,就绑定好数据。

注意:在开发中很少使用bind方法,bind属于RAC中的底层方法,RAC已经封装了很多好用的其他方法,底层都是调用bind,用法比bind简单.

下面例子中要实现当 testField 的文字改变以后每次输出"输出:**",下面的信号订阅方法底层会转换成bind方法

//使用 bind 方法
[[_textField.rac_textSignal bind:^RACStreamBindBlock{
return ^RACStream *(id value, BOOL *stop){
return [RACReturnSignal return:[NSString stringWithFormat:@"输出:%@",value]];
};

}] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
//直接订阅信号
[_textField.rac_textSignal subscribeNext:^(id x) { NSLog(@"输出:%@",x); }];

过滤信号的操作

fliter : 过滤信号(条件过滤)

  • 通过返回bool的方式控制是否接受信号传递过来的值(控制是否调用subscribeNext这个block)
  • fliter会将接受的信号通过返回的条件进行筛选,并且生成新的信号供订阅者订阅.
  • 从fliter的使用中可以看出RAC中对信号的操作都会生成新的信号,以便达到链式编程的目的
  • 从这点建议在对信号的操作的代码书写规范:每一次操作都应该折行,这样阅读起来更加清晰

本例中要实现当用户名长度大于3的时候输出log

[[self.usernameTextField.rac_textSignal
filter:^BOOL(id value){
NSString*text = value;
return text.length > 3;
}] subscribeNext:^(id x){
NSLog(@"%@", x);
}];

ignore:

忽略某些信号的值

// 内部调用filter过滤,忽略掉ignore的值 [[_textField.rac_textSignal ignore:@"1"]
subscribeNext:^(id x) {
NSLog(@"%@",x);
}];

distinctUntilChanged:

当上一次的值和当前的值有明显的变化就会发出信号, 在开发中,刷新UI经常使用,只有两次数据不一样才需要刷新,提高性能,减少不必要的操作

[[_textField.rac_textSignal distinctUntilChanged] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];

take

从信号发出的值中取前n个

RACSubject *signal = [RACSubject subject];
[[signal take:1] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
[signal sendNext:@1];
[signal sendNext:@2];

takeLast:

取最后N次的信号,前提条件,由于是从信号的后面取,所以订阅者必须调用完成,因为只有完成,就知道总共有多少信号.

RACSubject *signal = [RACSubject subject];
[[signal takeLast:1] subscribeNext:^(id x)
NSLog(@"%@",x);
}];
[signal sendNext:@1];
[signal sendNext:@2];
//必须有下一步!
[signal sendCompleted];

takeUntil:(RACSignal *):

获取信号直到某个信号执行完成

[_textField.rac_textSignal
takeUntil:self.rac_willDeallocSignal];

skip:(NSUInteger):

跳过几个信号,不接受。

// 表示输入第一次,不会被监听到,跳过第一次发出的信号
[[_textField.rac_textSignal skip:1] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];

switchToLatest:

用于signalOfSignals(信号的信号),有时候信号也会发出信号,会在signalOfSignals中,获取signalOfSignals发送的最新信号。

RACSubject *signalOfSignals = [RACSubject subject];
RACSubject *signal = [RACSubject subject];

// 获取信号中信号最近发出信号,订阅最近发出的信号。
// 注意switchToLatest:只能用于信号中的信号
[signalOfSignals.switchToLatest subscribeNext:^(id x) {

NSLog(@"%@",x);
}];
[signalOfSignals sendNext:signal];
[signal sendNext:@1];

Map:

(映射) map能够"加工信号传递的值”

这里的加工信号指的是将上一个next事件传递过来的信号值x进行加工或者转换成任意的OC对象,通过block返回值返回回去,这个例子中就是将用户名加工成NSNumber值返回回去,所以下一个next事件接收到的值就是NSNumber对象.

[[[self.usernameTextField.rac_textSignal
map:^id(NSString*text){
return @(text.length);
}]
filter:^BOOL(NSNumber*length){
return[length integerValue] > 3;
}]
subscribeNext:^(id x){
NSLog(@"%@", x):
}];

//test2
[[validPasswordSignal
map:^id(NSNumber *passwordValid){
return[passwordValid boolValue] ? [UIColor clearColor]:[UIColor yellowColor];
}]
subscribeNext:^(UIColor *color){
self.passwordTextField.backgroundColor = color;
}];

flatten map: 信号的映射

flatten可以对传递过来的信号进行加工,会重新新建一个信号,block中的返回值是一个信号

[[_textField.rac_textSignal flattenMap:^RACStream *(id value) {
return [RACReturnSignal return:[NSString stringWithFormat:@"输出:%@",value]]; }]
subscribeNext:^(id x) {
}];

flattenMap 和 Map 方法的区别:

Reduce

聚合: 将多个信号发出的值进行聚合

常用用法:通常使用

+ (RACSignal *)combineLatest:(id<NSFastEnumeration>)signals reduce:(id (^)())reduceBlock;

将多个信号进行聚合,在reduceBlock中将信号传递的进行整合.这个方法会创建一个新的信号携带整合好的值返回,每次这两个源信号的任何一个产生新值时,reduce block都会执行,block的返回值会发给下一个信号。常见应用场景就是登录界面是否满足登录条件从而控制登录按钮的状态

注意这个方法中的reduceBlock: 这个block是一个返回值为id类型的,也就是说我们将需要聚合的信号携带的值进行加工组合以后一定要返回一个新的对象,combineLatest::这个方法返回的信号会携带这个新对象! 我们再注意看reduceBlock中的参数,参数个数是不确定的,他们和信号中携带的值一一对应,为了实现这个功能RAC中专门有个RACBlockTrampoline类来处理这个逻辑


RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {

[subscriber sendNext:@1];

return nil;
}];

RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {

[subscriber sendNext:@2];

return nil;
}];

RACSignal *reduceSignal = [RACSignal combineLatest:@[signalA,signalB] reduce:^id(NSNumber *num1 ,NSNumber *num2){

return [NSString stringWithFormat:@"%@ %@",num1,num2];

}];

[reduceSignal subscribeNext:^(id x) {

NSLog(@"%@",x);
}];

ReactiveCocoa操作方法之秩序(控制流操作)。

doNext:

执行Next之前,会先执行这个Block

doCompleted:

执行sendCompleted之前,会先执行这个Block

[[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@1];
[subscriber sendCompleted];
return nil;
}] doNext:^(id x) {
// 执行[subscriber sendNext:@1];之前会调用这个Block
NSLog(@"doNext");;
}] doCompleted:^{
// 执行[subscriber sendCompleted];之前会调用这个Block
NSLog(@"doCompleted");; }] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];

ReactiveCocoa中的线程操作

deliverOn:

内容传递切换到指定线程中,副作用在原来线程中,把在创建信号时block中的代码称之为副作用。

subscribeOn:

内容传递和副作用都会切换到指定线程中。

ReactiveCocoa操作方法之时间控制

timeout:

超时,可以让一个信号在一定的时间后,自动报错。

RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
return nil;
}] timeout:1 onScheduler:[RACScheduler currentScheduler]];

[signal subscribeNext:^(id x) {
NSLog(@"%@",x);
} error:^(NSError *error) {
// 1秒后会自动调用
NSLog(@"%@",error);
}];

interval

定时:每隔一段时间发出信号

[[RACSignal interval:1 onScheduler:[RACScheduler currentScheduler]] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];

delay

延迟发送next。

RACSignal *signal = [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@1];
return nil;
}] delay:2] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];

ReactiveCocoa操作方法之重复。

retry

重试 :只要失败,就会重新执行创建信号中的block,直到成功.

throttle

节流:当某个信号发送比较频繁时,可以使用节流,在某一段时间不发送信号内容,过了一段时间获取信号的最新内容发出。

RACSubject *signal = [RACSubject subject];
_signal = signal;
// 节流,在一定时间(1秒)内,不接收任何信号内容,过了这个时间(1秒)获取最后发送的信号内容发出。
[[signal throttle:1] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
上一篇下一篇

猜你喜欢

热点阅读