冷信号与热信号
- 什么是冷信号与热信号
- Signal vs Subject
- 冷信号 -> 热信号
一 什么是冷信号与热信号
- 当 Signal 有多个订阅者
- 转换的本质
- (
RACSignal
*)bind:(
RACStreamBindBlock
(^)(
void
))block;
{
return
[
RACSignal
createSignal
:^
RACDisposable
*(
id
<
RACSubscriber
> subscriber) {
RACStreamBindBlock
bindBlock = block();
[
self
subscribeNext:^(
id
x) {
BOOL
stop =
NO
;
RACSignal *signal = (RACSignal *)bindBlock(x, &stop);
if
(signal ==
nil
|| stop) { [subscriber sendCompleted];
}
else
{
[signal subscribeNext:^(
id
x) { [subscriber sendNext:x];
} error:^(NSError *error) { [subscriber sendError:error];
} completed:^{ }];
}
} error:^(NSError *error) { [subscriber sendError:error];
} completed:^{ [subscriber sendCompleted]; }];
return
nil
;
}]; }
- Signal 变换后多个订阅者
- Signal订阅者共享
- 冷热信号对比
看点播 vs 看直播
冷信号剧本,订阅舞台剧
热信号舞台剧,订阅观看舞台剧
- 热信号在哪里
RACSubject
RACSubject
*subject = [
RACSubject
subject
];
[subject subscribeNext:^(
id
x) {
// a
} error:^(NSError *error) {
// b
} completed:^{
// c
}];
[subject sendNext:
@1
];
[subject sendNext:
@2
];
[subject sendNext:
@3
];
[subject sendCompleted];
RACSuject和它的子类(RACSignal 的子类)
- RACReplaySubjcet
- 带快速回播的Subject
- 控制"历史值"的数量
RACReplaySubject
*subject = [
RACReplaySubject
replaySubjectWithCapacity
:
1
];
[subject sendNext:
@1
];
[subject sendNext:
@2
];
[subject sendCompleted];
[subject subscribeNext:^(
id
x) {
/* a*/
}];
RACSignal vs RACSubject
- 冷信号 vs 热信号
- 确定的未来 vs 不确定的未来
- 无视订阅者 vs 关心订阅者
RACSubject的建议
- 等同于变量,有很强的使用吸引力
- 可用做全局通知
- 尽量少用,用在热信号的场景上
冷信号
—
> 热信号
- 一个人看视频 —> 叫上大家一起看
- 视频文件(剧本) —> 播放器 + 显示屏 + 音响 —> 一堆观众
- RACSignal —> RACSubject —> Subscribers
RACSignal
*signal =
@[@1
,
@2
,
@3
,
@4]
.rac_sequence.signal;
RACSignal
*signalB = [[signal map:^
id
(
id
value) {
return
[[RACSignal
return
:value] delay:
1
];
}] concat];
RACSubject
*speaker = [
RACSubject
subject
];
[signalB subscribe:speaker];
[speaker subscribeNext:^(
id
x) {
// a }];
[speaker subscribeNext:^(
id
x) {
// b }];
[speaker subscribeNext:^(
id
x) {
// c }];
冷信号
—
> 热信号 官方方案
- (
RACMulticastConnection
*)publish;
- (RACMulticastConnection *)multicast:(RACSubject *)subject;
- (
RACSignal
*)replay;
- (RACSignal *)replayLast;
- (
RACSignal
*)replayLazily;
冷信号+副作用方法(在订阅的时候才能确定)
+ (
RACSignal
*)defer:(
RACSignal
* (^)(
void
))block;
- (RACSignal *)then:(RACSignal * (^)(
void
))block;
不建议使用的同步方法
(减少全局变量,减少 RACSubject)
- (
id
)first;
- (
id
)firstOrDefault:(
id
)defaultValue;
- (
id
)firstOrDefault:(
id
)defaultValue
success:(
BOOL
*)success
error:(NSError **)error;
- (
BOOL
)waitUntilCompleted:(NSError **)error;
- (NSArray *)toArray;
@property
(nonatomic, strong, readonly)
RACSequence *sequence;
其它方法
- (RACSignal *)groupBy:(
id
<NSCopying> (^)(
id
object))keyBlock;
- (RACSignal *)groupBy:(
id
<NSCopying> (^)(
id
object))keyBlock
transform:(
id
(^)(
id
object))transformBlock;
RACSignal
*signal =
@[@1
,
@2
,
@3
,
@4]
.rac_sequence.signal;
RACSignal *signalGroup = [signal groupBy:^NSString *(NSNumber *object) {
return
object.integerValue %
2
==
0
?
@"odd"
:
@"even"
;
}];
[[[signalGroup take:
1
] flatten] subscribeNext:^(
id
x) {
NSLog(
@"next: %@"
, x);
}];
二 RAC 并发编程
- 同步和异步
- 并行和并发
- RACScheduler
- Signal 遇上并发
1.同步
- 函数调用,不返回结果不进行下一步
- 书写顺序 == 执行顺序
- 阻塞 IO
2.异步
- 函数调用,直接进行下一步
- 通过回调函数返回结果
- 书写顺序 != 执行顺序
- 非阻塞 IO
- RAC 整个是个异步库
3.并发
- 在一个物理计算核心
- 通过调度手段兼顾多个任务
- 是任务看似一起执行
- "美女餐厅"游戏
并发
并行
- 在多个物理计算核心
- 通过分配手段处理多个任务
- 使任务一起执行
- 多个服务的餐厅
并行
如何在 RAC 中并发编程?
RACScheduler
"Schedulers are used to control when and where work is performed
…
"
RACScheduler 一览
RACScheduler示例
//
主线程的
Scheduler
RACScheduler *mainScheduler = [RACScheduler mainThreadScheduler];
//
子线程的
2
个
Scheduler,
注意
[RACSchedul schduler]
是返回一个新的
RACScheduler
*scheduler1 = [
RACScheduler
scheduler
];
RACScheduler *scheduler2 = [RACScheduler scheduler];
//
返回当前的
Scheduler,
自定义线程会返回
nil
RACScheduler *scheduler3 = [RACScheduler currentScheduler];
//
创建某优先级
Scheduler,
不建议除非你知道你在干什么
RACScheduler
*scheduler4 = [
RACScheduler
schedulerWithPriority
:
RACSchedulerPriorityHigh
];
RACScheduler *scheduler5 = [RACScheduler
schedulerWithPriority
:RACSchedulerPriorityHigh
name:
@"someName"
];
//
创建立即
Scheduler,
不建议除非你知道你在干什么
RACScheduler *scheduler6 = [RACScheduler immediateScheduler];
//
分派一个任务
,[disposable dispose]
用来取消
RACDisposable *disposable = [mainScheduler schedule:^{
/*
这里是个任务
*/
}]; [disposable dispose];
//
定时任务
NSDateFormatter
*formatter = [[NSDateFormatter alloc] init];
formatter
.
dateFormat
=
@"yyyy-MM-dd HH:mm:ss"
;
NSDate *date = [formatter dateFromString:
@"2016-07-20 21:00:00"
];
[scheduler1 after:date schedule:^{
/* 2016-07-20 21:00:00
执行
*/
}];
//
延时任务
[scheduler2 afterDelay:
30
schedule:^{
/*
将在
30
秒后执行
*/
}];
//
循环任务
[scheduler3 after:[NSDate date] repeatingEvery:
1
withLeeway:
0.1
schedule:^{
//
从现在开始
,
每
1
秒执行一次
,
最长不能操作
1.1
秒执行下一次
}];
RACScheduler vs GCD
- Scheduler使用 GCD 来实现
- 可以"取消"
- 与RAC 其他组件高度整合
- 一个 Scheduler 保证串行执行
- 一个 Scheduler 的任务不保证线程是同一个
当 Signal 遇上并发
- 重温无并发的情况
- 异步订阅
- 异步发送
- 排列组合一下
- 解决之道
重温订阅顺序
NSLog(
@"start test"
);
RACSignal
*signal = [RACSignal createSignal:^RACDisposable *(
id
<RACSubscriber> subscriber) {
NSLog(
@"sendNext:@1"
);
[subscriber sendNext:
@1
];
NSLog(
@"sendNext:@2"
);
[subscriber sendNext:
@2
];
NSLog(
@"sendCompleted"
);
[subscriber sendCompleted];
NSLog(
@"return nil"
);
return
nil
;
}];
NSLog(
@"signal was created"
);
[signal subscribeNext:^(
id
x) {
NSLog(
@"receive next:%@"
, x);
} error:^(NSError *error) {
NSLog(
@"receive error:%@"
, error);
} completed:^{
NSLog(
@"receive complete"
);
}];
NSLog(
@"subscribing finished"
);
情景之一:异步订阅
void
subscribeAsync()
{
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"
);
}
情景之二:异步发送
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"
);
}
情景之三:同步+异步发送
发送在不同的 Scheduler
void
sendEverywhere()
{
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"
);
}
回一下 Merge 操作 signalA发生在主线程,signalB发生在子线程,merge后主线程和子线程都有可能发生
情景之四:异步订阅+异步发送
订阅和发送都在不同的 Scheduler
void
sendAndSubscribeEverywhere
()
{
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"
);
}
存在的问题:
- 订阅时机不确定 —> subscribeOn: (发送都在主线程)
- 发送时机不确定 —> deliverOn: (接收都在主线程)
使用 subscribeOn
void
useSubscribeOn()
{
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
subscribeOn
:[
RACScheduler
mainThreadScheduler
]]
subscribeNext
:^(
id
x) {
NSLog
(
@"%@"
, x);
}]; }];
NSLog
(
@"444"
);
}
subscribeOn:总结
- 能够保证 didSubscribe block 在指定的 scheduler
- 不能保证 sendNext,error 和 complete 在哪个 scheduler
- 头文件描述
/// Use of this operator should be avoided whenever possible, because the
/// receiver's side effects may not be safe to run on another thread. If you just
/// want to receive the signal's events on `scheduler`, use -deliverOn: instead.
- (RACSignal *)subscribeOn:(RACScheduler *)scheduler;
使用 deliverOn:
void
useDeliverOn()
{
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
deliverOn
:[
RACScheduler
mainThreadScheduler
]]
subscribeNext
:^(
id
x) {
NSLog
(
@"%@"
, x);
}]; }];
NSLog
(
@"444"
);
}
subscribeOn:的用武之地
void
whenShouldWeUseSubscribeOn()
{
UIView
*view = [[
UIView
alloc]
init
];
RACSignal
*signal = [
RACSignal
createSignal:^RACDisposable *(
id
<RACSubscriber> subscriber) {
UILabel
*label = [[UILabel alloc] init];
label.
text
=
@"Hello world"
;
[view
addSubview
:label];
[subscriber sendNext:
@0.1
];
RACDisposable *
disposable
= [[RACScheduler scheduler] schedule:^{
[subscriber
sendNext
:
@1.1
];
[subscriber sendCompleted];
}];
return
disposable;
}];
[[
RACScheduler
scheduler
]
schedule
:^{
[[
signal
subscribeOn:[RACScheduler mainThreadScheduler]] subscribeNext:^(
id
x) {
NSLog(
@"%@"
, x);
}];
}]; }
总结:
- 单个信号转换
- 多个信号组合
- 高阶信号操作
- 冷热信号操作
- 并发操作
一、判断题:
冷信号订阅后就变成热信号是错误的(
热信号与冷信号订阅后统称为信号)
一个热信号进行信号变换,会得到一个冷信号
有副作用的冷信号多次订阅,得到的结果无法预期
一个
button
的
rac_signalForControlEvent
返回的信号是一个热信号(所有信号的接收
一个
scheduler
就是一个线程是错误的 (一个 scheduler就是一个能保证串行 queue异步工作者)
二、思考题:
(
1
)学习一下
GroupBy
这个方法,然后分析下下面这段代码中的错误是什么?
```
- (RACSignal *)groupBy:(id<NSCopying> (^)(id object))keyBlock;
- (RACSignal *)groupBy:(id<NSCopying> (^)(id object))keyBlock
transform:(id (^)(id object))transformBlock;
RACSignal *signal = @[@1, @2, @3, @4].rac_sequence.signal;
RACSignal *signalGroup = [signal groupBy:^NSString *(NSNumber *object) {
return object.integerValue % 2 == 1 ? @"odd" : @"even";
}];
[[[signalGroup take:1] flatten] subscribeNext:^(id x) {
NSLog(@"next: %@", x);
}];
```
1-2-3-4->
||
\/ GroupBy
odd-even->
| |
\1-3 |
\2-4
||
\/ Take:1
odd->
|
\1-3
||
\/ Flatten
1-3->
||
\/ Subscribe
1->
不应该使用
Take:1
的方法
正确写法:
[[[signalGroup fliter:^BOOL(RACGroupSubject *group) {
return [group.name isEqualToString:@"odd"];
}] flatten] subscribeNext:^(id x) {
NSLog(@"next: %@", x);
}];
*
(
2
)学习了冷信号与热信号,我们可以说热信号是重要的,冷信号是不重要的么?请列举冷热信号分别的使用场景。
冷热信号同等重要
*
(
3
)
ReactiveCocoa
给了我们一下
5
种方法把一个冷信号转换为热信号,请分别描述他们的作用与关系:
```
- (RACMulticastConnection *)publish;
- (RACMulticastConnection *)multicast:(RACSubject *)subject;
- (RACSignal *)replay;
- (RACSignal *)replayLast;
- (RACSignal *)replayLazily;
```
见美团
·
点评技术博客
* (4)
观察下面代码,判断
`
NSLog(@"!!!")
`
执行的时候,
`
signal
`
对象是否
dealloc
了。实际运行一下,看看自己的判断是否正确,并说明原因。
```
void sendAsync()
{
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"111");
RACDisposable *disposable = [[RACScheduler mainThreadScheduler] schedule:^{
NSLog(@"!!!");
[subscriber sendNext:@1];
[subscriber sendCompleted];
}];
return disposable;
}];
NSLog(@"222");
[signal subscribeNext:^(id x) {
NSLog(@"333");
}];
NSLog(@"444");
}
```
NSLog(@"!!!");
在这时,
signal
已经释放