君不见黄河之水天上来,奔流到海不复回。
君不见高堂明镜悲白发,朝如青丝暮成雪。
人生得意须尽欢,莫使金樽空对月。
天生我材必有用,千金散尽还复来。
烹羊宰牛且为乐,会须一饮三百杯。
岑夫子,丹丘生,将进酒,杯莫停。
与君歌一曲,请君为我侧耳听。
钟鼓馔玉不足贵,但愿长醉不复醒。
古来圣贤皆寂寞,惟有饮者留其名。
陈王昔时宴平乐,斗酒十千恣欢谑。
主人何为言少钱,径须沽取对君酌。
五花马,千金裘,
呼儿将出换美酒,与尔同销万古愁。——李白《将进酒·君不见黄河之水天上来》
ReactiveCocoa
- 是函数响应编程(Functional Reactive Programming, FRP)框架
- 实际的开发中是和MVVM结合进行开发、互相弥补
Github上整合的比较全面的(MVVM、RAC)学习资料
美团的几篇资料 (开发中可能遇到的坑)
- 细说ReactiveCocoa的冷信号与热信号1
- 细说ReactiveCocoa的冷信号与热信号2
- 细说ReactiveCocoa的冷信号与热信号3
- RACSignal的Subscription深入分析
注意点
- RAC只处理对象, 而不处理像BOOL这样的原始值. 不过, RAC通常会帮我们自动做这些转换.
使用Cocoapods导入ReactiveCocoa 报错 ld: library not found for -lPods
- 解决方法 : Target’s General settings and go to Linked Frameworks and Libraries and just delete -libPods.a from the list.
信号的释放
- ReactiveCocoa维护了一个全局的信号集合。
- 如果信号有一个或多个订阅者,它就是可用的。如果所有订阅者都被移除了,信号就被释放了。
信号的取消订阅
- 在一个completed事件或error事件后,一个订阅者会自动将自己移除。
- 手动移除可能通过RACDisposable来完成。RACSignal的所有订阅方法都返回一个RACDisposable实例,我们可以调用它的dispose方法来手动移除订阅者。
- 如果我们创建了一个信号,但不去订阅它,则信号永远不会执行,包括任何如doNext:这样的附加操作。
- Signal events是线性的,不会出现并发的情况,除非显式地指定Scheduler。所以subscribeNext:error:completed:里的block不需要锁定或者synchronized等操作,其他的events会依次排队,直到block处理完成。
- 生成Signal时,最好指定Name, -setNameWithFormat: 方便调试
- Side Effect (副作用)
- Hot signals without side effects 最好使用property,如“textChanged”,不太理解什么情况用到这个,权当做一个静态的属性来看就行。
- Cold signals without side effects 使用名词类型的方法名,如“-currentText”,“currentModels”,同时表明了返回值是什么(这个尤其得注意,RACSignal的next值是id类型,所以全得是靠约定才知道具体返回类型)
- Signals with side effects 这种就是像login一样有副作用的了,推荐使用动词类型的方法名,用对动词基本就能知道是不是有副作用了,比如“-loginSignal”和“-saveToFile”大概就知道前面一个很可能有副作用,后面一个多存几次文件应该没副作用
RAC VS 原生
rac_signalForSelector
:用于替代代理rac_valuesAndChangesForKeyPath
:用于监听某个对象的属性改变rac_signalForControlEvents
:用于监听某个事件。rac_addObserverForName
:用于监听某个通知rac_textSignal
:只要文本框发出改变就会发出这个信号- 处理当界面有多次请求时,需要都获取到数据时,才能展示界面
rac_liftSelector:withSignalsFromArray:Signals
:当传入的Signals(信号数组),每一个signal都至少sendNext过一次,就会去触发第一个selector参数的方法- 使用注意:几个信号,参数一的方法就几个参数,每个参数对应信号发出的数据
RAC提供的宏
RAC()
- 你提供想要与即将到来的值绑定的对象和参数, 在其内部它所做的是创建一个订阅者并更新其属性的值
这个宏是最常用的,RAC()总是出现在等号左边,等号右边是一个RACSignal,表示的意义是将一个对象的一个属性和一个signal绑定,signal每产生一个value(id类型),都会自动执行:[TARGET setValue:value ?: NIL_VALUE forKeyPath:KEYPATH];
1
2
3
4
5RACSignal *usernameIsValidSignal = RACObserve(self.viewModel, isUsernameValid);
RAC(self.goButton, enabled) = usernameIsValidSignal;
RAC(self.goButton, alpha) = [usernameIsValidSignal map:^id(NSNumber *usernameIsValid){
return usernameIsValid.boolValue ? @1.0 : @0.5;
}];
RACObserve()
这个宏是RAC中对KVO中那些API的替代. 你只需要传入对象和你想观察的那个对象某属性的 keypath. 给出这些参数后, RACObserve会创建一个信号, 一旦它有了订阅者, 它就立刻发送那个属性的当前值, 并在发送那个属性在这之后的任何变化.
1
2
3
4
5
6// create and get a reference to the signal
RACSignal *usernameValidSignal = RACObserve(self.viewModel, isUsernameValid);
// update the local property when this value changes
[usernameValidSignal subscribeNext: ^(NSNumber *isValidNumber) {
self.usernameIsValid = isValidNumber. boolValue
}];
循环引用的宏
- @weakify(self); // 在外面声明
- @strongify(self); // 在引用的地方声明
- 这两个宏一定成对出现,先weak再strong
常用知识点
- map :
- 将一个值转化为另一个值输出
- 将会创建一个和原来一模一样的信号,只不过新的信号传递的值变为了block(value),也就是说,如果block(value)是一个信号,那么就是信号的value仍然是信号。
flattenMap
: 解决Signal of Signals问题,flattenMap则会继续调用这个信号的value,作为新的信号的value。switchToLatest
: 用于signal of signals,获取signal of signals发出的最新信号,也就是可以直接拿到RACCommand中的信号filter
: 过滤一些条件,以确保符合条件的值才能被传递出去ignore
: 忽略给定的值注意,这里忽略的既可以是地址相同的对象,也可以是- isEqual:结果相同的值,也就是说自己写的Model对象可以通过重写- isEqual:方法来使- ignore:生效。常用的值的判断没有问题,如下:
1
2
3
4[[self.inputTextField.rac_textSignal ignore:@"seven"] subscribeNext:^(NSString *value)
{
NSLog(@"`seven` could never appear : %@", value);
}];
distinctUntilChanged
: 它将这一次的值与上一次做比较,当相同时(也包括- isEqual:)被忽略掉。1
2
3
4RAC(self.label, text) = [RACObserve(self.user, username) distinctUntilChanged];
self.user.username = @"sunnyxx"; // 1st
self.user.username = @"sunnyxx"; // 2nd
self.user.username = @"sunnyxx"; // 3rd- 如果不增加
distinctUntilChanged
的话对于连续的相同的输入值就会有不必要的处理,这个栗子只是简单的UI刷新,但遇到如写数据库,发网络请求的情况时,代价就不能购忽略了。 - 所以,对于相同值可以忽略的情况,果断加上它吧。
- 如果不增加
起止点过滤类型
- 除了被动的当next值来的时候做判断,也可以主动的提前选择开始和结束条件,分为两种类型:
take
型(取)skip
型(跳)
- 除了被动的当next值来的时候做判断,也可以主动的提前选择开始和结束条件,分为两种类型:
take:(NSUInteger)
: 从开始一共取N次的next值,不包括Competion
和Error
,如:1
2
3
4
5
6
7
8
9[[[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);
}];takeLast:(NSUInteger)
: 取最后N次的next值- 注意,由于一开始不能知道这个Signal将有多少个next值,所以RAC实现它的方法是将所有next值都存起来,然后原Signal完成时再将后N个依次发送给接收者
- 但Error发生时依然是立刻发送的
takeUntil:(RACSignal *)
: 当给定的signal完成前一直取值, 也就是这个Signal一直到textField执行dealloc时才停止。最简单的栗子就是UITextField的rac_textSignal的实现(删减版本):1
2
3
4
5
6
7
8
9- (RACSignal *)rac_textSignal {
@weakify(self);
return [[[[[RACSignal
concat:[self rac_signalForControlEvents:UIControlEventEditingChanged]]
map:^(UITextField *x) {
return x.text;
}]
takeUntil:self.rac_willDeallocSignal] // bingo!
}takeUntilBlock:(BOOL(^)(id x))
: 对于每个next值,运行block,当block返回YES时停止取值,如:1
2
3
4
5[[self.inputTextField.rac_textSignal takeUntilBlock:^BOOL(NSString *value) {
return [value isEqualToString:@"stop"];
}] subscribeNext:^(NSString *value) {
NSLog(@"current value is not `stop`: %@", value);
}];takeWhileBlock:(BOOL(^)(id x))
: 上面的反向逻辑,对于每个next值,block返回 YES时才取值skip:(NSUInteger)
: 从开始跳过N次的next值,简单的栗子:1
2
3
4
5
6
7
8
9[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"1"];
[subscriber sendNext:@"2"];
[subscriber sendNext:@"3"];
[subscriber sendCompleted];
return nil;
}] skip:1] subscribeNext:^(id x) {
NSLog(@"only 2 and 3 will be print: %@", x);
}];skipUntilBlock:(BOOL(^)(id x))
: 和- takeUntilBlock:同理,一直跳,直到block为YESskipWhileBlock:\(BOOL(^)(id x))
: 和- takeWhileBlock:同理,一直跳,直到block为NOmerge
: 合并信号,只要有一个信号变化就会调用1
2
3
4
5
6
7
8@weakify(self);
[[RACSignal merge: @[RACObserve(self.viewModel, tweets),
RACObserve(self.viewModel, allTweetsLoaded)]]
bufferWithTime: 0 onScheduler: [RACScheduler mainThreadScheduler]]
subscribeNext: ^(id value) {
@strongify(self);
[self.tableView reloadData];
}];combineLatest
: 将多个信号合并起来,并且拿到各个信号的最新的值,必须每个合并的signal至少都有过一次sendNext,才会触发合并的信号根据组合的信号的值再转换生成判断条件
1
2
3
4
5
6
7RACSignal *signupActiveSignal = [RACSignal combineLatest:@[validUsernameSignal,
validPwdSignal] reduce:^id(NSNumber *usernameValid, NSNumber *passwordValid) {
return @(usernameValid.boolValue && passwordValid.boolValue);
}];
[signupActiveSignal subscribeNext:^(NSNumber *state) {
self.signInButton.enabled = state.boolValue;
}];
doNext
: 注意doNext:并不返回一个值,因为它是附加操作。它完成时不改变事件。concat
: 按一定顺序拼接信号,当多个信号发出的时候,有顺序的接收信号 (只需要订阅拼接的信号就可以了)then
: then方法会等到completed事件发出后调用,然后订阅由then:block参数返回的信号。这有效地将控制从一个信号传递给下一个信号zipWith
: 把两个信号压缩成一个信号,只有当两个信号同时发出信号内容时,并且把两个信号的内容合并成一个元组,才会触发压缩流的next事件RACScheduler
: 是RAC里面对线程的简单封装,事件可以在指定的scheduler上分发和执行,- 不特殊指定的话,事件的分发和执行都在一个默认的后台线程里面做,大多数情况也就不用动了
- 有一些特殊的signal必须在主线程调用,使用-deliverOn:可以切换调用的线程。
deliverOn
: 更新UI的时候回到主线程执行(子线程加载图片、主线程刷新UI)1
deliverOn:[RACScheduler mainThreadScheduler]
chain
:multicast
: 避免副作用,这两个订阅者接收到了同样的一个请求的内容1
2
3
4
5
6
7
8
9
10
11// Starts a single request, no matter how many subscriptions `connection.signal`
// gets. This is equivalent to the -replay operator, or similar to
// +startEagerlyWithScheduler:block:.
RACMulticastConnection *connection = [networkRequest multicast:[RACReplaySubject subject]];
[connection connect];
[connection.signal subscribeNext:^(id response) {
NSLog(@"subscriber one: %@", response);
}];
[connection.signal subscribeNext:^(id response) {
NSLog(@"subscriber two: %@", response);
}];timeout
: 超时,可以让一个信号在一定的时间后,自动报错interval
: 定时,每隔一段时间发出信号delay
: 延迟发送nextretry重试
: 只要失败,就会重新执行创建信号中的block,直到成功replay重放
: 当一个信号被多次订阅,反复播放内容throttle
节流 : 当某个信号发送比较频繁时,可以使用节流,在某一段时间不发送信号内容,过了一段时间获取信号的最新内容发出- 延时处理,只有在两次next事件间隔指定的时间时才会发送第二个next事件