ReactiveCocoa的基本了解
ReactiveCocoa简称RAC。
RAC的基本结构
信号源
* RACStream
* RACSignal
* RACSubject
* RACSequence
订阅者
* RACSubscriber
* RACMulticastConnection
调度器
* RACScheduler
清洁工
* RACDisposable
RACSignal
能产生且只能产生三种事件:next、completed,error。
next 表示这个 Signal 产生了一个值
completed 表示 Signal 结束,结束信号只标志成功结束,不带值
-
error 表示 Signal 中出现错误,立刻结束
RACSignal
RAC核心是Signal,对应的类为
RACSignal
。它其实是一个信号源,Signal会给它的订阅者(Subscriber)发送一连串的事件,一个Signal可比作流水线中的一段管线,负责决定管线传输什么样的数据。Subscriber是Signal的订阅者,我们将Subscriber比作管线上的工人,它在拿到数据后对其进行加工处理。数据经过加工后要么进入下一条管线继续处理,要么直接当做成品使用。Subscriber
Subscriber我们一般称之为订阅者,它负责处理Signal传出的数据。
RACSubscriber
初始化的时候会传入nextBlock、 errorBlock、completeBlock,正是这三个block用于处理不同类型的数据信号(或是将处理后的数据抛往下一段管线,或是当做成品送给使用方)。Signal获取到数据后,会调用Subscriber的sendNext, sendComplete, sendError方法来传送数据给Subscriber,Subscriber自然也有方法来获取传过来的数据,如:[signal subscribeNext:error:completed]。这样只要没有sendComplete和sendError,新的值就会通过sendNext源源不断地传送过来。
下面用代码介绍RAC的基本使用和注意事项
RAC的信号分为冷信号和热信号
RACSignal
的休眠(cold)和激活(hot)状态,也就是所谓的冷信号和热信号。一般情况下,一个RACSignal
创建之后都处于cold状态,有人去subscribe才被激活。
RACSubject
不一样 创建成功就处于热状态 可发送可接受。对RACSubject
对象的每次subscription,都是将这个subscriber加到subscribers数组中,并没有调用 didSubScirbe()
信号的基本创建和订阅
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"qwqw"];
//遇到sendError或sendComplete 就结束发送信号
//[subscriber sendError:nil];
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
NSLog(@"这里是信号结束后需要操作什么,可以return nil");
}];
}];
//激活冷信号
[signal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
RACSignal
的副作用
RACSignal在被subscribe的时候可能会产生副作用(Side Effects),就是当一个冷信号被重复订阅的时,导致singnal里的代码重复执行,这可能是你需要的情况,但如果你不要这种情况出现可以用RACMulticastConnection
来处理这种情况。
mulitcast 这个方法,首先就创建了一个RACMulticastConnection
对象保存参数起来
connect 方法里面会对signal subscribe 也就是执行createBlock执行完毕。
signal就会调用清除方法。
然后用connection的信号订阅。
这时我们在后续操作的subscriNext的signal已经不是原来的signal了,而是didsubscribeBlock为空的signal,所以不管后面有多少次subscribNext都不会让createBlock重复执行。
RACMulticastConnection *connection = [signal multicast:[RACReplaySubject subject]];
[connection connect];
[connection.signal subscribeNext:^(id x) {
NSLog(@"one -> %@",x);
}];
[connection.signal subscribeNext:^(id x) {
NSLog(@"two -> %@",x);
}];
结果
2017-03-16 20:51:23.963 MVVMDemo[37845:1040952] 这里是信号结束后需要操作什么,可以return nil 2017-03-16 20:51:23.965 MVVMDemo[37845:1040952] one -> qwqw 2017-03-16 20:51:23.965 MVVMDemo[37845:1040952] two -> qwqw
RACSubject
RACSubject
:信号提供者,自己可以充当信号,又能发送信号。
RacSubject
创建成功就处于热状态 可发送可接受.对RACSubject
对象的每次subscription,都是将这个subscriber加到subscribers数组中,并没有调用 didSubScirbe()
//RACSubject 要提前订阅再发送信号 RACSubject是一对多的
RACSubject *subject = [RACSubject subject];
[subject subscribeNext:^(id x) {
NSLog(@"subject -> %@",x);
}];
[subject subscribeNext:^(id x) {
NSLog(@"subject2 -> %@",x);
}];
[subject sendNext:@(123456)];
RAC使用时的注意事项
使用时需要注意循环引用,@weakify(self) / @strongify(self) 组合解除循环引用;
@weakify(self)
[[self.loginButton rac_signalForControlEvents:UIControlEventTouchUpInside]
subscribeNext:^(id x) {
@strongify(self)
[self.viewModel.loginCommand execute:nil];
}];
RAC里把系统很多事件都封装好,我们可以很方便调用并转换为信号模式
1.例如target-action,UIControl的addTaget方法。
[[self.loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(UIButton *button) {
NSLog(@"登录按钮被点击");
}];
2.通知
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"kClickButton" object:nil] subscribeNext:^(NSNotification *notification) {
//收到通知
NSLog(@"收到通知");
}];
3.KVO
[RACObserve(self.loginBtn, highlighted) subscribeNext:^(id x) {
NSLog(@"loginBtn.Highlighted->%@",x);
}];
4.RAC也把基本控件都封装了,方便开发者使用。
[self.userIdTF.rac_textSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
RAC宏
RAC宏允许直接把信号的输出应用到对象的属性上。RAC宏有两个参数,第一个是需要设置属性值的对象,第二个是属性名。每次信号产生一个next事件,传递过来的值都会应用到该属性上。
RAC( self.userPWTF, backgroundColor) = [validPasswordSignal map:^id(NSNumber *value) {
return [value boolValue] ? [UIColor clearColor] : [UIColor redColor];
}];
RACCommand类用于表示事件的执行
command的初始化方法中有一个enabledSignal参数,这个signal就是用来指明command能否被执行的。
signalBlock参数在command需要执行时调用,这个block需要返回一个signal用来表示正在执行,之前将allowsConcurrentExecute的值设置为默认值NO,此时command会观察这个signal,而且在这个执行进度完成前,不允许新的执行。如果你需要手动执行command,可以发送消息:-[RACCommand execute:()]
RACCommand *btnCommand = [[RACCommand alloc] initWithEnabled:validUserNameSignal signalBlock:^RACSignal *(id input) {
return [self loginSignal];
}];
RACSequence类
,可以简单看做是RAC世界的NSArray,RAC增加了-rac_sequence方法,可以使诸如NSArray这些集合类(collection classes)直接转换为RACSequence来使用.
NSArray *arr = @[@1,@2,@3,@4,@5,@6];
[arr.rac_sequence.signal subscribeNext:^(id x) {
//NSLog(@"x:%@",x);
}];
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:@"jtd",@"name",@"man",@"sex",@"jx",@"jg", nil];
[dict.rac_sequence.signal subscribeNext:^(id x) {
RACTupleUnpack(NSString *key,NSString *value) = x;
NSLog(@"key:%@,value:%@",key,value);
//NSLog(@"%@",x);
}];
RACScheduler 调度器 控制线程
//RACScheduler 调度器 控制线程
//startEagerlyWithScheduler Eagerly立即 Lazily稍后
//schedulerWithPriority 指定等级的异步并发队列
//信号传递到那个线程deliverOn -> mainThreadScheduler(主线程) currentScheduler(当前线程)
RAC(self.avatarIV,image) = [[RACSignal startEagerlyWithScheduler:[RACScheduler schedulerWithPriority:RACSchedulerPriorityBackground] block:^(id<RACSubscriber> subscriber) {
NSError *error;
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://ww3.sinaimg.cn/bmiddle/7128be06jw1ei4hfthoj3j20hs0bomyd.jpg"]
options:NSDataReadingMappedAlways
error:&error];
if(error) {
[subscriber sendError:error];
}else{
[subscriber sendNext:[UIImage imageWithData:data]];
[subscriber sendCompleted];
}
}] deliverOn:[RACScheduler mainThreadScheduler]];
RAC信号的处理
RACSignal的每个操作都会返回一个RACsignal。
转换(map)、过滤(filter) 取双层信号中内层信号的值:flattenMap等操作。
1.filter过滤
[[self.userIdTF.rac_textSignal filter:^BOOL(NSString *value) {
return value.length > 4;//大于4才开启管道 YES开启 NO关闭
}] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
2.map转换
新加的map操作通过block改变了事件的数据。map从上一个next事件接收数据,通过执行block把返回值传给下一个next事件。在上面的代码中,map以NSString为输入,取字符串的长度,返回一个NSNumber。
[[[self.userIdTF.rac_textSignal map:^id(NSString *text) {
return @(text.length);//返回对象类型 改变的对象类型
}] filter:^BOOL(NSNumber *value) {
return [value integerValue] > 3;
} ] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
3.concat:按一定顺序拼接信号
当多个信号发出的时候,有顺序的接收信号。
RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence;
RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;
// Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9
RACSequence *concatenated = [letters concat:numbers];
[concatenated.signal subscribeNext:^(id x) {
// 注意:第一个信号必须发送完成,第二个信号才会被激活
//NSLog(@"concatenated - >%@",x);
}];
4.flatten (合并)
switchToLatest 的原理是当有新的signal来的时候,就dispose老的signal,订阅新的signal,而 flatten 不会 dispose 老的 signal
RACSubject *oneSubject = [RACSubject subject];
RACSubject *twoSubject = [RACSubject subject];
RACSignal *signalOfSignals = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
[subscriber sendNext:oneSubject];
[subscriber sendNext:twoSubject];
[subscriber sendCompleted];
return nil;
}];
RACSignal *flattened = [signalOfSignals flatten];
// Outputs: A 1 B C 2
[flattened subscribeNext:^(NSString *x) {
//NSLog(@"%@", x);
}];
[oneSubject sendNext:@"A"];
[twoSubject sendNext:@"1"];
[oneSubject sendNext:@"B"];
[oneSubject sendNext:@"C"];
[twoSubject sendNext:@"2"];
5.flattenMap 把源信号的内容映射成一个新的信号,信号可以是任意类型
开发中,如果信号发出的值不是信号,映射一般使用Map。如果信号发出的值是信号,映射一般使用FlatternMap。Map底层其实是调用flatternMap,Map中block中的返回的值会作为flatternMap中block中的值。
//把textfield的输入操作转换成另一种输出[NSString stringWithFormat:@"输出:%@",value]的信号
[[self.userPWTF.rac_textSignal flattenMap:^RACStream *(id value) {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:[NSString stringWithFormat:@"输出:%@",value]];
return nil;
}];
}] subscribeNext:^(id x) {
NSLog(@"flattenMap - >%@",x);
}];
6.merge:把多个信号合并为一个信号,任何一个信号有新值的时候就会调用
//
[[RACSignal merge:@[self.userIdTF.rac_textSignal,self.userPWTF.rac_textSignal]] subscribeNext:^(id x) {
NSLog(@"merge - >%@",x);
}];
7.combineLatest(组合)将多个信号合并起来
combineLatest(组合)将多个信号合并起来,并且拿到各个信号的最新的值,必须每个合并的signal至少都有过一次sendNext,才会触发合并的信号。
8.switchToLatest(选择最新的信号)
//
RACSubject *switchSignal = [RACSubject subject];
RACSubject *signalA = [RACSubject subject];
RACSubject *signalB = [RACSubject subject];
// 获取信号中信号最近发出信号,订阅最近发出的信号。
// 注意switchToLatest:只能用于信号中的信号
[signalOfSignals.switchToLatest subscribeNext:^(id x) {
NSLog(@"switchToLatest-> %@",x);
}];
[switchSignal sendNext:signalA];
[switchSignal sendNext:signalB];
[signalA sendNext:@1];
[signalB sendNext:@2];
9.ignore(忽略)
忽略给定的值,注意,这里忽略的既可以是地址相同的对象,也可以是- isEqual:结果相同的值,也就是说自己写的Model对象可以通过重写- isEqual:方法来使- ignore:生效。常用的值的判断没有问题,
[[self.userIdTF.rac_textSignal ignore:@"sunny"] subscribeNext:^(NSString *value) {
NSLog(@"`sunny` could never appear : %@", value);
}];
10 take(取)
从开始一共取N次的next值,不包括Competion和Error
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"1"];
[subscriber sendNext:@"2"];
[subscriber sendNext:@"3"];
[subscriber sendCompleted];
return nil;
}] take:2] subscribeNext:^(id x) {
NSLog(@"only 1 and 2 will be print: %@", x);
}];
11 takeUntil(取值,直到某刻结束)
当给定的signal完成前一直取值。
[self.userIdTF.rac_textSignal takeUntil:self.rac_willDeallocSignal];
12.takeUntilBlock(对于每个next值,运行block,当block返回YES时停止取值)
[[self.userIdTF.rac_textSignal takeUntilBlock:^BOOL(NSString *value) {
return [value isEqualToString:@"stop"];
}] subscribeNext:^(NSString *value) {
NSLog(@"current value is not `stop`: %@", value);
}];
13then:用于连接两个信号
当第一个信号完成,才会连接then返回的信号
//
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@1];
//第一个信号没有触发sendCompleted 不会触发then
[subscriber sendCompleted];
return nil;
}] then:^RACSignal *{
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@2];
return nil;
}];
}] subscribeNext:^(id x) {
// 只能接收到第二个信号的值,也就是then返回信号的值
NSLog(@"then -> %@",x);
}];
14.distinctUntilChanged:当上一次的值和当前的值有明显的变化就会发出信号,否则会被忽略掉。
过滤,当上一次和当前的值不一样,就会发出内容。在开发中,刷新UI经常使用,只有两次数据不一样才需要刷新
[[self.userIdTF.rac_textSignal distinctUntilChanged] subscribeNext:^(id x) {
NSLog(@"distinctUntilChanged -> %@",x);
}];
15.timeout 超时,可以让一个信号在一定的时间后,自动报错。
RACSignal *timeOutSignal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
return nil;
}] timeout:1 onScheduler:[RACScheduler currentScheduler]];
[timeOutSignal subscribeNext:^(id x) {
NSLog(@"timeOutSignal -> %@",x);
} error:^(NSError *error) {
// 1秒后会自动调用
NSLog(@"timeOut error-> %@",error);
}];
16.interval 定时:每隔一段时间发出信号
//
[[RACSignal interval:1 onScheduler:[RACScheduler currentScheduler]] subscribeNext:^(id x) {
//返回当前时间
NSLog(@"interval 1ms -> %@",x);
}];
17.delay 延迟发送信号
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@1];
return nil;
}] delay:2] subscribeNext:^(id x) {
NSLog(@"delay 2s-> %@",x);
}];
18. retry重试 :只要失败,就会重新执行创建信号中的block,直到成功.
__block int i = 0;
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
if (i == 10) {
[subscriber sendNext:@1];
}else{
[subscriber sendError:nil];
}
i++;
return nil;
}] retry] subscribeNext:^(id x) {
NSLog(@"retrySignal -> %@",x);
} error:^(NSError *error) {
NSLog(@"接收到错误");
}];
19.throttle节流:当某个信号发送比较频繁时,可以使用节流,在某一段时间不发送信号内容,过了一段时间获取信号的最新内容发出。
RACSubject *throttleSignal = [RACSubject subject];
// 节流,在一定时间(1秒)内,不接收任何信号内容,过了这个时间(1秒)获取最后发送的信号内容发出。
[[throttleSignal throttle:1] subscribeNext:^(id x) {
NSLog(@"throttleSignal -> %@",x);
}];
20.reduce聚合信号
reduceblcok中的参数,有多少信号组合,reduceblcok就有多少参数,每个参数就是之前信号发出的内容
reduceblcok的返回值:聚合信号之后的内容。
[[RACSignal combineLatest:@[validPasswordSignal,validUserNameSignal] reduce:^id(NSNumber *usernameValid, NSNumber *passwordValid){
return @([usernameValid boolValue] && [passwordValid boolValue]);
}] subscribeNext:^(NSNumber *signupActive) {
self.loginBtn.enabled = [signupActive boolValue];
}];
21.doNext:
你可以看到doNext:是直接跟在按钮点击事件的后面。而且doNext: block并没有返回值。因为它是附加操作,并不改变事件本身。
//
[[[[self.loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside] doNext:^(id x) {
self.loginBtn.enabled = NO;
}] flattenMap:^RACStream *(id value) {
return [self loginSignal];
}] subscribeNext:^(id x) {
self.loginBtn.enabled = YES;
}];
上面的信号操作只是RAC中的一小部分,以后会不定时更新。
可以留意我的博客:lemonfan.cn