ReactiveCocoa技术讲解-第五讲并发编程
同步 && 异步
同步:函数调用,不返回结果不进行下一步。
异步:函数调用,直接进行下一步,通过回调函数返回结果。
并发 && 并行
并发:在一个物理计算核心(cpu),通过调度手段兼顾多个任务,使任务看似一起执行。
并发.png
图示:
并行:在多个物理计算核心(cpu),通过分配手段处理多个任务,使任务一起执行。
并行图示:
屏幕快照 2017-12-20 下午9.37.08.png
注意:
并发任务从宏观上来看:十多个任务同时被执行了, 但是微观上来说还是串行执行的。这点看图大家会很清楚。
RACScheduler 使用
1、创建scheduler:
RACscheduler的6个类方法.png
2、执行任务:
执行任务.png
注意:取消任务执行
操作dispose
是一个伪取消,后面会明为什么是个伪取消。
RACScheduler同GCD对比
通过上面的操作我们看到RACScheduler同GCD在代码书写和使用上有很大的相似之处,那二者之间关系到底有没有关系呢?下面就详细对比下。
1、RACScheduler 是对GCD的高级封装,它是使用GCD来实现的。
2、RACScheduler创建的任务是可以“取消”的,当然这个取消是一个伪取消,原因是:取消后,实际上仍然会执行,只不过执行时回调函数没有调用而已。因为RACScheduler是基于GCD实现的,而GCD中并没有取消操作,所以RACScheduler只能是一个高级的封装。
3、一个scheduler中的任务是一定是串行执行的。(如果需要两个任务同时执行,则可以创建2个scheduler)。
4、同一个scheduler中的任务不能保证是同一条线程来执行。
(GCD的串行queue,不能保证执行的线程是同一个。线程字典,现场堆栈,如果不能保证是同一条线成,线程字典写入时会有问题。同样schedule也是不能保证线程是同一个需要注意。
)
信号订阅的顺序:(多线程中信号订阅的执行逻辑):
1、普通的信号订阅执行过程:
2、异步订阅
- (void)subscriberAsync {
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {\
NSLog(@"111");
[subscriber sendNext:@"1"];
[subscriber sendCompleted];
return nil;
}];
[[RACScheduler scheduler] schedule:^{ //异步订阅
NSLog(@"222");
[signal subscribeNext:^(id x) {
NSLog(@"333");
}];
}];
NSLog(@"444");
}
//console:444 - 222 - 111 - 333
3、异步发送
- (void)sendAsync {
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {\
NSLog(@"111");
RACDisposable *disposable = [[RACScheduler scheduler] schedule:^{
[subscriber sendNext:@"1"];
[subscriber sendCompleted];
}];
return disposable;
}];
NSLog(@"222");
[signal subscribeNext:^(id x) {
NSLog(@"333");
}];
NSLog(@"444");
}
//console: 222 -> 111 -> 444 ->333
4、同步订阅,异步发送
- (void)sendEverywhere {
//在不同的schedule发送
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {\
NSLog(@"111");
[subscriber sendNext:@0.1];
RACDisposable *disposable = [[RACScheduler scheduler] schedule:^{
[subscriber sendNext:@1.1];
[subscriber sendCompleted];
}];
return disposable;
}];
NSLog(@"222");
[signal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
NSLog(@"444");
}
//console:2 1 0.1 444
//console: 1.1
5、异步订阅,异步发送
- (void)sendAsyncAndsubscruberAsync {
//在不同的schedule发送
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {\
NSLog(@"111");
[subscriber sendNext:@0.1];
RACDisposable *disposable = [[RACScheduler scheduler] schedule:^{
[subscriber sendNext:@1.1];
[subscriber sendCompleted];
}];
return disposable;
}];
[[RACScheduler scheduler] schedule:^{
NSLog(@"222");
[signal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
}];
NSLog(@"444");
}
信号订阅执行逻辑:
1)
subscribeNext:
永远和didSubscriber block
绑在一起。也就是只要调用subscribeNext:,紧接着一定会执行这个block。
2) 在didSubscriber这个block中执行sendNext,sendComplete会立即在当前线程
执行订阅者传过来的block(也就是subscribeNext:^{ //代码 }
这个block)。
尤其是要注意的是:在当前线程中执行subscribeNext block
。
总结:上面的4中情况(出去1),我们会发现信号的返回值可能在出现不同的线程中
,这就很难控制,例如我们有一个网络请求,我们异步发起一个网络请求,然后让线程常驻以便接受请求返回值,如果出现上面的情况,网络请求的返回值跑到了另一条线程上去。这就可能不符合我们的预期。从而出现一些异常。
为了解决上面的问题,让信号返回值在指定的线程中返回,RAC挺高了两种解决办法:
1、subscriberOn: 在指定的线程执行didsubscriber这个block
(1)使用subscriberOn:后,信号订阅以及信号返回值所在线程的例子:
subscriberOn:.png
(2)subscriberOn:只能保证didsubscriber block在指定的scheduler执行,但是不能保证sendNext 、sendComplete在哪个scheduler执行。
注:didsubscriber这个block 是指:创建信号过程中,执行信号订阅 的代码块
2、deliverOn: 在指定的线程接受信号返回值。
(1)使用deliverOn:后的信号值所在的线程图示:
deliverOn.png
步骤讲解上图:
1)在执行deliverOn:subscribeNext:后,会立即执行didSubscribe这个block。注意这里同subscribeOn:不同
2)执行sendNext时:通常情况下,会在当前线程中
立即执行订阅者发送过来的block,但是由于使用了deliverOn:,它实际上是将订阅者的block放到了指定线程的schedule中
。所以这里会稍后在指定的线程中执行这个订阅者block(上图这里就是mainThreadScheduler)。
3)新创建的schedule中执行sendNext:同样会在主线程中执行订阅者的block,因为订阅者是同一个
,他提供的block同样被放到指定线程的schedule中。
4)注意:主线程中的0.1前面的“scheduler计划表”是由[RACScheduler mainThreadScheduler]创建的一个新的计划表。下面的线程是由中间最后一个schedule创建的。(当线程存在时,只会往线程中添加一个scheduler,当线程不存在时,会先创建一条线程,然后添加schedule
).一个schedule是一个串行queue,它是基于GCD封装的。