RAC 使用技巧

实用技巧

使用 RAC 添加一个 backgroundTask

1
2
3
4
5
6
7
8
9
10
11
UIBackgroundTaskIdentifier backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
NSLog(@"Ran out of time");
}];
 
RACSignal *taskSignal = [self.repository save];
 
[taskSignal subscribeError:^(NSError *error) {
[[UIApplication sharedApplication] endBackgroundTask:backgroundTask];
} completed:^{
[[UIApplication sharedApplication] endBackgroundTask:backgroundTask];
}];

观察消息的正确姿势

https://github.com/ReactiveCocoa/ReactiveCocoa/issues/365

1
2
3
4
5
6
7
8
9
10
11
12
// X
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIApplicationDidBecomeActiveNotification object:nil]
subscribeNext:^(id x) {
// do something
}];
// √
[[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIApplicationDidBecomeActiveNotification object:nil]
takeUntil:[self rac_willDeallocSignal]]
subscribeNext:^(id x) {
// do something
}];

doAroundNext

https://github.com/ReactiveCocoa/ReactiveCocoa/issues/1731

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (RACSignal *)doAroundNext:(void (^)(id x, void (^yield)()))nextBlock {
NSCParameterAssert(nextBlock != NULL);
return [[RACSignal create:^(id<RACSubscriber> subscriber) {
[self subscribeNext:^(id x) {
void (^yielder)() = ^{
[subscriber sendNext:x];
};
nextBlock(x, yielder);
} error:^(NSError *error) {
[subscriber sendError:error];
} completed:^{
[subscriber sendCompleted];
}];
}] setNameWithFormat:@"[%@] -%@", self.name, NSStringFromSelector(_cmd)];
}

RAC 定时器

如果需要倒计时到0,可以改最后的x.intValue < 0, 如果不需要开始的值,去掉startWith, 订阅一般是收获的阶段,所以尽量不要在订阅的时候加以控制

1
2
3
4
5
6
7
8
9
+ (RACSignal *)makeTimer(int times) {
RACSignal *timer = [RACSignal interval:1 onScheduler:[RACScheduler mainThreadScheduler]];
return [[[timer scanWithStart:@(times) reduce:^id(NSNumber *running, id _) {
return @(running.intValue - 1);
}] startWith:@(times)]
takeUntilBlock:^BOOL(NSNumber *x) {
return x.intValue == 0;
}];
}

在TableViewCell 的 subView 中绑定事件

https://github.com/ReactiveCocoa/ReactiveCocoa/issues/490

1
2
3
4
5
6
7
8
9
10
11
12
13
[[[cell.okButton
rac_signalForControlEvents:UIControlEventTouchUpInside]
takeUntil:cell.rac_prepareForReuseSignal]
subscribeNext:^(UIButton *x) {
MXThing *thing = self.things[indexPath.row];
// use this thing
}];
[[[cell.cancelButton rac_signalForControlEvents:UIControlEventTouchUpInside]
takeUntil:cell.rac_prepareForReuseSignal]
subscribeNext:^(UIButton *x) {
// do other things
}];

注意 NSCalendar 的使用

内存泄漏

https://zhuanlan.zhihu.com/p/22090471

Operator 操作注意

simple 的使用

https://github.com/ReactiveCocoa/ReactiveCocoa/issues/977

1
[RACSignal zip:@[ signalA, [signalB sample:signalA] ]]

理解 switchToLatest

解释了就没意思了,大家思考下几个重点的问题吧:

  1. 为什么要publish self?
  2. 对connection.signal做FlattenMap干什么?
  3. 对next value里面的signal为什么要做takeUntil? 而且还是connection.signal
  4. 为什么要concat一个never信号?
  5. 最后的return的disposable为什么这样写?
  6. 分析下取消订阅的过程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (RACSignal *)switchToLatest {
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
RACMulticastConnection *connection = [self publish];
RACDisposable *subscriptionDisposable = [[connection.signal
flattenMap:^(RACSignal *x) {
NSCAssert(x == nil || [x isKindOfClass:RACSignal.class], @"-switchToLatest requires that the source signal (%@) send signals. Instead we got: %@", self, x);
// -concat:[RACSignal never] prevents completion of the receiver from
// prematurely terminating the inner signal.
return [x takeUntil:[connection.signal concat:[RACSignal never]]];
}]
subscribe:subscriber];
RACDisposable *connectionDisposable = [connection connect];
return [RACDisposable disposableWithBlock:^{
[subscriptionDisposable dispose];
[connectionDisposable dispose];
}];
}] setNameWithFormat:@"[%@] -switchToLatest", self.name];
}

defer

为什么要延迟创建信号呢?是因为创建信号的某些必要的量或者方法只在那时才有效。比如一个信号表示当前时间,需要一个[NSDate date]值,如果创建信号的时候给了[RACSignal return:[NSDate date]],就是未来订阅的时候,得到的仍然是创建的时间。但是用defer就可以订阅得到那时的时间了。

RACObserve cold or hot?

首先它这个冷信号的意义,就是发旧的值和未来的变化值。所以是一个副作用的冷信号,它能保证每次订阅都是按照这个规则,所以各个订阅之间其实没有关系
相当于 [RACSignal retrun:@1]无论订阅多少次都是一样的效果,这个是可以随便重复订阅的,从某种意义上讲,也算是热信号

热信号

如果要拆分信号,在拆分前的信号转换成热信号,防止重复计算
replay 会一直计算过去的值,内存会爆掉,使用 replayLast 会比较好

swichToLatest

dispose 上一个,订阅下一个

flatten:

参数和纬度无关,只和效率相关,参数相当于当前的并发数

contact

相当于 Flatten:1