学计算机的那个

不是我觉到、悟到,你给不了我,给了也拿不住;只有我觉到、悟到,才有可能做到,能做到的才是我的.

0%

ReactiveCocoa学习

RAC 被描述为函数响应式编程。

编程思想

函数式编程 :使用高阶函数,例如函数用其他函数作为参数。
响应式编程:关注于数据流和变化传播。
链式编程 : 是将多个操作(多行代码)通过点号(.)链接在一起成为一句代码,使代码可读性好。a(1).b(2).c(3),注意点:要想达到链式编程方法的返回值必须是一个( (返回值是本身对象的)block)

入门

入门第一部分

Signal 传递的 data

Signal 传递的 data 是 event,它所传递的 event 包括 3 种:值事件、完成事件和错误事件。其中在传递值事件时,可以携带数据.传递值事件/完成事件/错误事件的本质就是向 subscriber 发送sendNext:、sendComplete以及sendError:消息

Signal 在其生命周期内,可以传递任意多个值事件,但最多只能传递一个完成事件或错误事件;换句话说,一旦 Signal 的事件流中出现了错误事件或者完成事件,之后产生的任何事件都是无效的.

Signal的简单使用

创建信号、订阅信号、订阅过程

创建获取信号
  1. 创建单元信号
  2. 创建动态信号
  3. 通过 Cocoa 桥接
  4. 从别的信号变换而来
  5. 由序列变换而来

Cocoa桥接

1
2
3
4
RACSignal *signal6 = [objectrac_signalForSelector:@selector(setFrame:)];
RACSignal *signal7 = [control rac_signalForControlEvents:UIControlEventTouchUpInside];
RACSignal *signal8 = [object rac_willDeallocSignal];
RACSignal *signal9 = RACObserve(object, keyPath);

信号变换

1
2
3
RACSignal *signal10 = [signal1 map:^id(id value) {
return someObject;
}];

序列变换

1
RACSignal *signal11 = sequence.signal;
订阅信号

订阅信号的方式有 3 种:

  1. 通过subscribeNext:error:completed:方法订阅
  2. RAC 宏绑定
  3. Cocoa 桥接

通过subscribeNext:error:completed:方法订阅

1
2
3
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
error:(void (^)(NSError *error))errorBlock
completed:(void (^)(void));

RAC 宏绑定

1
2
RAC(view, backgroundColor) = signal10;
// 每当signal10产生一个值事件,就将view.backgroundColor设为相应的值

Cocoa 桥接

1
2
3
[object rac_liftSelector:@selector(someSelector:) withSignals:signal1, signal2, nil];
[object rac_liftSelector:@selector(someSelector:) withSignalsFromArray:@[signal1, signal2]];
[object rac_liftSelector:@selector(someSelector:) withSignalOfArguments:signal1];
订阅过程

订阅过程指的是信号被订阅的处理逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"1"];
[subscriber sendNext:@"2"];
[subscriber sendCompleted];
[subscriber sendNext:@"3"]; // 无效
return [RACDisposable disposableWithBlock:^{
NSLog(@"dispose"); // 当错误事件或者完成事件产生时,该block被调用
}];
}];

[signal subscribeNext:^(id x) {
NSLog(@"next value is : %@", x);
} error:^(NSError *error) {
NSLog(@"error : %@", error);
} completed:^{
NSLog(@"completed");
}];

/* prints:
next value is : 1
next value is : 2
completed
dispose
*/

注意:

  • RACSignal的每一个操作都会返回一个RACSignal。

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

  • RAC中信号处理的常用方法

  1. Map实现类型转换
  2. 信号中的信号 flattenMap
  3. 添加附加的操作 doNext
  4. Reduce 聚合: 将多个信号发出的值进行聚合
  • ReactiveCocoa操作方法之秩序(控制流操作)。
  1. doNext:执行Next之前,会先执行这个Block
  2. doCompleted:
    执行sendCompleted之前,会先执行这个Block

RACObserve接收不到信号问题

1
2
3
4
5
6
7
8
9
[RACObserve(self, selectedRows) subscribeNext:^(NSSet *currentlySelected) {
NSLog(@"Currently selected: %@", currentlySelected);
}];

self.selectedRows = [NSMutableSet set];
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.selectedRows addObject:indexPath];
}

上面RACObserve回调只会调用一次,初始化的时候,为什么addObject的时候没有出发信号?
因为addObject没有触发KVO事件。解决通过协议触发KVO

1
2
3
4
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[[self mutableSetValueForKey:@"selectedRows"] addObject:indexPath];
}

入门第二部分

  • 取消订阅
    在一个completed或者error事件之后,订阅会自动移除,还可以通过RACDisposable手动移除订阅。

  • 链接signal
    Then方法会等待completed事件的发送,然后再订阅由then block返回的signal。这样就高效地把控制权从一个signal传递给下一个。

  • 线程
    deliverOn

  • 节流
    throttle

进阶

RACSignal的subscription过程

RACSignal的常见用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-(RACSignal *)signInSignal {
// part 1:[RACSignal createSignal]来获得signal
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[self.signInService
signInWithUsername:self.usernameTextField.text
password:self.passwordTextField.text
complete:^(BOOL success) {

// part 3: 进入didSubscribe,通过[subscriber sendNext:]来执行next block
[subscriber sendNext:@(success)];
[subscriber sendCompleted];
}];
return nil;
}];3
}

// part 2 : [signal subscribeNext:]来获得subscriber,然后进行subscription
[[self signInSignal] subscribeNext:^(id x) {
NSLog(@"Sign in result: %@", x);
}];

Subscription过程概括
RACSignal的Subscription过程概括起来可以分为三个步骤:

  1. [RACSignal createSignal]来获得signal

  2. [signal subscribeNext:]来获得subscriber,然后进行subscription

  3. 进入didSubscribe,通过[subscriber sendNext:]来执行next block

  4. 步骤一:[RACSignal createSignal]来获得signal

1
2
3
4
5
6
7
8
9
10
RACSignal.m中:
+ ( RACSignal *)createSignal:( RACDisposable * (^)( id < RACSubscriber > subscriber))didSubscribe {
return [ RACDynamicSignal createSignal :didSubscribe];
}
RACDynamicSignal.m中
+ ( RACSignal *)createSignal:( RACDisposable * (^)( id < RACSubscriber > subscriber))didSubscribe {
RACDynamicSignal *signal = [[ self alloc ] init ];
signal-> _didSubscribe = [didSubscribe copy ];
return [signal setNameWithFormat : @"+createSignal:" ];
}

[RACSignal createSignal]会调用子类RACDynamicSignal的createSignal来返回一个signal,并在signal中保存后面的 didSubscribe这个block

  1. 步骤二:[signal subscribeNext:]来获得subscriber,然后进行subscription
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
RACSignal.m中:
- ( RACDisposable *)subscribeNext:( void (^)( id x))nextBlock {
RACSubscriber *o = [ RACSubscriber subscriberWithNext :nextBlock error : NULL completed : NULL ];
return [ self subscribe :o];
}
RACSubscriber.m中:

+ ( instancetype )subscriberWithNext:( void (^)( id x))next error:( void (^)( NSError *error))error completed:( void (^)( void ))completed {
RACSubscriber *subscriber = [[ self alloc ] init ];
subscriber-> _next = [next copy ];
subscriber-> _error = [error copy ];
subscriber-> _completed = [completed copy ];
return subscriber;
}
RACDynamicSignal.m中:
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];
if (self.didSubscribe != NULL) {
RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
RACDisposable *innerDisposable = self.didSubscribe(subscriber);
[disposable addDisposable:innerDisposable];
}];
[disposable addDisposable:schedulingDisposable];
}
return disposable;
}
  1. [signal subscribeNext]先会获得一个subscriber,这个subscriber中保存了nextBlock、errorBlock、completedBlock

  2. 由于这个signal其实是RACDynamicSignal类型的,这个[self subscribe]方法会调用步骤一中保存的didSubscribe,参数就是1中的subscriber

  3. 步骤三:进入didSubscribe,通过[subscriber sendNext:]来执行next block

1
2
3
4
5
6
7
8
RACSubscriber.m中:
- (void)sendNext:(id)value {
@synchronized (self) {
void (^nextBlock)(id) = [self.next copy];
if (nextBlock == nil) return;
nextBlock(value);
}
}

任何时候这个[subscriber sendNext:],就直接调用nextBlock

multicast和replay

有关的RACSignal的操作

在RACSignal+Operation.h中,定义了5个跟我们这个主题有关的RACSignal的操作

1
2
3
4
5
6
7
8
9
10
RACSignal+Operation.h中
- (RACMulticastConnection *)publish;

- (RACMulticastConnection *)multicast:(RACSubject *)subject;

- (RACSignal *)replay;

- (RACSignal *)replayLast;

- (RACSignal *)replayLazily;

这几个操作的区别很细微,但用错的话很容易出问题。只有理解了原理之后,才明白它们之间的细微区别,很多时候我们意识不到需要用这些操作,这就可能因为side effects执行多次而导致程序bug

multicast && replay的应用场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 引用ReactiveCocoa源码的Documentation目录下的一个例子
// This signal starts a new request on each subscription.
RACSignal *networkRequest = [RACSignal createSignal:^(id<RACSubscriber> subscriber) {
AFHTTPRequestOperation *operation = [client
HTTPRequestOperationWithRequest:request
success:^(AFHTTPRequestOperation *operation, id response) {
[subscriber sendNext:response];
[subscriber sendCompleted];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[subscriber sendError:error];
}];

[client enqueueHTTPRequestOperation:operation];
return [RACDisposable disposableWithBlock:^{
[operation cancel];
}];
}];

// 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);
}];
  1. 在上面的例子中,如果我们不用RACMulticastConnection的话,那就会因为执行了两次subscription而导致发了两次网络请求。
  2. 从上面的例子中,我们可以看到对一个Signal进行multicast之后,我们是对connection.signal进行subscription而不是原来的networkRequest。这点是”side effects should only occur once”的关键,我们将在后面解释

冷信号和热信号

冷热信号的概念源于.NET框架Reactive Extensions(RX)中的Hot Observable和Cold Observable,两者的区别是:

  1. Hot Observable是主动的,尽管你并没有订阅事件,但是它会时刻推送,就像鼠标移动;而Cold Observable是被动的,只有当你订阅的时候,它才会发布消息。

  2. Hot Observable可以有多个订阅者,是一对多,集合可以与订阅者共享信息;而Cold Observable只能一对一,当有不同的订阅者,消息是重新完整发送。

冷信号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-(void)racColdSingle
{
RACSignal* coldSingle=[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@1];
[subscriber sendNext:@2];
[subscriber sendNext:@3];
[subscriber sendCompleted];
return nil;
}];

NSLog(@"Signal was created.");
[[RACScheduler mainThreadScheduler] afterDelay:0.1 schedule:^{
[coldSingle subscribeNext:^(id x) {
NSLog(@"Subscriber 1 recveive: %@", x);
}];
}];

[[RACScheduler mainThreadScheduler] afterDelay:1 schedule:^{
[coldSingle subscribeNext:^(id x) {
NSLog(@"Subscriber 2 recveive: %@", x);
}];
}];
}
1
2
3
4
5
6
7
8
2019-09-23 14:14:26.853285+0800 RACDemo[16585:117906] Signal was created.
2019-09-23 14:14:26.956126+0800 RACDemo[16585:117906] Subscriber 1 recveive: 1
2019-09-23 14:14:26.956339+0800 RACDemo[16585:117906] Subscriber 1 recveive: 2
2019-09-23 14:14:26.956559+0800 RACDemo[16585:117906] Subscriber 1 recveive: 3
2019-09-23 14:14:27.888112+0800 RACDemo[16585:117906] Subscriber 2 recveive: 1
2019-09-23 14:14:27.888267+0800 RACDemo[16585:117906] Subscriber 2 recveive: 2
2019-09-23 14:14:27.888363+0800 RACDemo[16585:117906] Subscriber 2 recveive: 3

信号在14:14:26.853时被创建,14:14:26.956依次接到1、2、3三个值,而在14:14:27.888再依次接到1、2、3三个值。说明了变量名为coldSingle的这个信号,在两个不同时间段的订阅过程中,分别完整地发送了所有的消息。

热信号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
-(void)racHoltSingal
{
RACMulticastConnection *connection = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[[RACScheduler mainThreadScheduler] afterDelay:1 schedule:^{
[subscriber sendNext:@1];
}];

[[RACScheduler mainThreadScheduler] afterDelay:2 schedule:^{
[subscriber sendNext:@2];
}];

[[RACScheduler mainThreadScheduler] afterDelay:3 schedule:^{
[subscriber sendNext:@3];
}];

[[RACScheduler mainThreadScheduler] afterDelay:4 schedule:^{
[subscriber sendCompleted];
}];
return nil;
}] publish];

[connection connect];
RACSignal *signal = connection.signal;

NSLog(@"Signal was created.");
[[RACScheduler mainThreadScheduler] afterDelay:1.1 schedule:^{
[signal subscribeNext:^(id x) {
NSLog(@"Subscriber 1 recveive: %@", x);
}];
}];

[[RACScheduler mainThreadScheduler] afterDelay:2.1 schedule:^{
[signal subscribeNext:^(id x) {
NSLog(@"Subscriber 2 recveive: %@", x);
}];
}];
}

来一一分析:

  1. 创建了一个信号,在1秒、2秒、3秒分别发送1、2、3这三个值,4秒发送结束信号。
  2. 对这个信号调用publish方法得到一个RACMulticastConnection。
  3. 让connection进行连接操作。
  4. 获得connection的信号。
  5. 分别在1.1秒和2.1秒订阅获得的信号。
1
2
3
4
2019-09-23 14:20:26.214718+0800 RACDemo[16872:122429] Signal was created.
2019-09-23 14:20:28.325190+0800 RACDemo[16872:122429] Subscriber 1 recveive: 2
2019-09-23 14:20:29.274356+0800 RACDemo[16872:122429] Subscriber 1 recveive: 3
2019-09-23 14:20:29.274537+0800 RACDemo[16872:122429] Subscriber 2 recveive: 3
  • [RACSignal publish]、- [RACMulticastConnection connect]、- [RACMulticastConnection signal]这几个操作生成了一个热信号

信号在14:20:26.214被创建14:20:28.325时订阅者1才收到2这个值,说明1这个值没有接收到,时间间隔是2秒多14:20:29.274时订阅者1和订阅者2同时收到3这个值,时间间隔是3秒多

热信号的如下特点:

  1. 热信号是主动的,即使你没有订阅事件,它仍然会时刻推送。如第二个例子,信号在50秒被创建,51秒的时候1这个值就推送出来了,但是当时还没有订阅者。而冷信号是被动的,只有当你订阅的时候,它才会发送消息。如第一个例子。
  2. 热信号可以有多个订阅者,是一对多,信号可以与订阅者共享信息。如第二个例子,订阅者1和订阅者2是共享的,他们都能在同一时间接收到3这个值。而冷信号只能一对一,当有不同的订阅者,消息会从新完整发送。如第一个例子,我们可以观察到两个订阅者没有联系,都是基于各自的订阅时间开始接收消息的。
RAC中的热信号

在RAC中,所有的热信号都属于一个类RACSubject

A subject, represented by the RACSubject class, is a signal that can be manually controlled.

Subjects can be thought of as the “mutable” variant of a signal, much like NSMutableArray is for NSArray. They are extremely useful for bridging non-RAC code into the world of signals.

For example, instead of handling application logic in block callbacks, the blocks can simply send events to a shared subject instead. The subject can then be returned as a RACSignal, hiding the implementation detail of the callbacks.

Some subjects offer additional behaviors as well. In particular, RACReplaySubject can be used to buffer events for future subscribers, like when a network request finishes before anything is ready to handle the result.

Subject具备如下三个特点:

  1. Subject是可变的
  2. Subject是非RAC到RAC的一个桥梁
  3. Subject可以附加行为,例如RACReplySubject具备为未来订阅者缓冲事件的能力。
  1. RACSubject几其子类是热信号
  2. RACSingle排除RACSubject类以外的是冷信号
冷信号转化成热信号—广播

冷信号与热信号的本质区别在于是否保持状态,冷信号的多次订阅是不保持状态的,而热信号的多次订阅可以保持状态,所以一种将冷信号转换为热信号的方法就是,将冷信号订阅,订阅到的每一个时间通过RACSubject发送出去,其它订阅者只订阅这个RACSubject

文章参考

  1. ReactiveCocoa学习之路
  2. Reactivecocoa(RAC)知其所以然
  3. 美团ReactiveCocoa