ReactiveCocoa冷热信号与并发编程

来源:互联网 发布:阿里云学生专区 编辑:程序博客网 时间:2024/04/28 20:04
冷信号与热信号
  • 什么是冷信号与热信号
  • Signal vs Subject
  • 冷信号 -> 热信号

一  什么是冷信号与热信号
  • 当 Signal 有多个订阅者

  • 转换的本质
- (RACSignal*)bind:(RACStreamBindBlock(^)(void))block;
{
   return [RACSignalcreateSignal:^RACDisposable*(id<RACSubscriber> subscriber) {
       RACStreamBindBlock bindBlock = block();
        [selfsubscribeNext:^(idx) {
           BOOL stop =NO;
            RACSignal *signal = (RACSignal *)bindBlock(x, &stop);
           if (signal ==nil || stop) { [subscriber sendCompleted];
            }else {
                [signal subscribeNext:^(idx) { [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 = [RACSubjectsubject];
[subject subscribeNext:^(idx) {
   // 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:^(idx) {  /* 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(idvalue) {
   return [[RACSignal return:value] delay:1];
}] concat];
RACSubject*speaker = [RACSubjectsubject];
[signalB subscribe:speaker];
[speaker subscribeNext:^(idx) {  // a }];
[speaker subscribeNext:^(idx) {  // b }];
[speaker subscribeNext:^(idx) {  // 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> (^)(idobject))keyBlock;
- (RACSignal *)groupBy:(id<NSCopying> (^)(idobject))keyBlock
             transform:(id(^)(idobject))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:^(idx) {
    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];
//子线程的2Scheduler,注意[RACSchedul schduler]是返回一个新的
RACScheduler*scheduler1 = [RACSchedulerscheduler];
RACScheduler *scheduler2 = [RACScheduler scheduler];
//返回当前的Scheduler,自定义线程会返回nil
RACScheduler *scheduler3 = [RACScheduler currentScheduler];
//创建某优先级Scheduler,不建议除非你知道你在干什么
RACScheduler*scheduler4 = [RACSchedulerschedulerWithPriority:RACSchedulerPriorityHigh];
RACScheduler *scheduler5 = [RACSchedulerschedulerWithPriority: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:30schedule:^{/* 将在30秒后执行*/ }];
//循环任务
[scheduler3 after:[NSDate date] repeatingEvery:1withLeeway:0.1schedule:^{
//从现在开始,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:^(idx) {
    NSLog(@"receive next:%@", x);
} error:^(NSError *error) {
    NSLog(@"receive error:%@", error);
} completed:^{
    NSLog(@"receive complete");
}];
NSLog(@"subscribing finished");
情景之一:异步订阅
voidsubscribeAsync()
{
   RACSignal *signal = [RACSignalcreateSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        NSLog(@"111");
        [subscriber sendNext:@1];
        [subscriber sendCompleted];
       return nil;
    }];
    [[RACSchedulerscheduler] schedule:^{
       NSLog(@"222");
        [signal subscribeNext:^(idx) {
            NSLog(@"333");
        }]; }];
    NSLog(@"444");
}

情景之二:异步发送
voidsendAsync()
{
   RACSignal *signal = [RACSignalcreateSignal:^RACDisposable*(id<RACSubscriber> subscriber) {
       NSLog(@"111");
       RACDisposable *disposable = [[RACSchedulerscheduler]schedule:^{
            [subscribersendNext:@1];
            [subscribersendCompleted];
            }];
       return disposable;
    }];
   NSLog(@"222");
    [signalsubscribeNext:^(idx) {
       NSLog(@"333");
    }];
   NSLog(@"444");
}

情景之三:同步+异步发送
发送在不同的 Scheduler
voidsendEverywhere()
{
   RACSignal *signal = [RACSignalcreateSignal:^RACDisposable*(id<RACSubscriber> subscriber) {
        NSLog(@"111");
        [subscriber sendNext:@0.1];
       RACDisposable *disposable = [[RACSchedulerscheduler]schedule:^{
            [subscribersendNext:@1.1];
            [subscribersendCompleted];
        }];
       return disposable;
    }];
   NSLog(@"222");
    [signalsubscribeNext:^(idx) {
       NSLog(@"%@", x);
    }];
   NSLog(@"444");
}



回一下 Merge 操作  signalA发生在主线程,signalB发生在子线程,merge后主线程和子线程都有可能发生


情景之四:异步订阅+异步发送
订阅和发送都在不同的 Scheduler
voidsendAndSubscribeEverywhere()
{
    RACSignal *signal= [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
       NSLog(@"111");
        [subscribersendNext:@0.1];
        RACDisposable *disposable= [[RACScheduler scheduler] schedule:^{
              [subscriber sendNext:@1.1];
            [subscriber sendCompleted];
        }];
       return disposable;
    }];
    [[RACSchedulerscheduler] schedule:^{
       NSLog(@"222");
        [signal subscribeNext:^(idx) {
            NSLog(@"%@", x);
        }]; }];
    NSLog(@"444");
}




存在的问题:
  • 订阅时机不确定  > subscribeOn: (发送都在主线程)
  • 发送时机不确定  —> deliverOn:  (接收都在主线程)

使用 subscribeOn
voiduseSubscribeOn()
{
   RACSignal *signal = [RACSignalcreateSignal:^RACDisposable*(id<RACSubscriber> subscriber) {
       NSLog(@"111");
        [subscribersendNext:@0.1];
       RACDisposable *disposable = [[RACSchedulerscheduler]schedule:^{
            [subscribersendNext:@1.1];
            [subscribersendCompleted];
        }];
       return disposable;
    }];
    [[RACSchedulerscheduler]schedule:^{
       NSLog(@"222");
        [[signalsubscribeOn:[RACSchedulermainThreadScheduler]]subscribeNext:^(idx) {
           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:
voiduseDeliverOn()
{
   RACSignal *signal = [RACSignalcreateSignal:^RACDisposable*(id<RACSubscriber> subscriber) {
       NSLog(@"111");
        [subscribersendNext:@0.1];
       RACDisposable *disposable = [[RACSchedulerscheduler]schedule:^{
            [subscribersendNext:@1.1];
            [subscribersendCompleted];
        }];
       return disposable;
    }];
    [[RACSchedulerscheduler]schedule:^{
       NSLog(@"222");
        [[signaldeliverOn:[RACSchedulermainThreadScheduler]]subscribeNext:^(idx) {
           NSLog(@"%@", x);
            }]; }];
   NSLog(@"444");
}



subscribeOn:的用武之地
voidwhenShouldWeUseSubscribeOn()
{
   UIView *view = [[UIViewalloc]init];
   RACSignal *signal = [RACSignalcreateSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
       
       UILabel *label = [[UILabel alloc] init];
        label.text= @"Hello world";
        [viewaddSubview:label];
        [subscriber sendNext:@0.1];
        RACDisposable *disposable= [[RACScheduler scheduler] schedule:^{
            [subscribersendNext:@1.1];
            [subscriber sendCompleted];
        }];
       return disposable;
    }];
    [[RACSchedulerscheduler]schedule:^{
        [[signalsubscribeOn:[RACScheduler mainThreadScheduler]] subscribeNext:^(idx) {
            NSLog(@"%@", x);
        }];
    }]; }


总结:
  • 单个信号转换
  • 多个信号组合
  • 高阶信号操作
  • 冷热信号操作
  • 并发操作


一、判断题:
冷信号订阅后就变成热信号是错误的(热信号与冷信号订阅后统称为信号)
一个热信号进行信号变换,会得到一个冷信号
有副作用的冷信号多次订阅,得到的结果无法预期
一个buttonrac_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)学习了冷信号与热信号,我们可以说热信号是重要的,冷信号是不重要的么?请列举冷热信号分别的使用场景。
冷热信号同等重要

*3ReactiveCocoa给了我们一下5种方法把一个冷信号转换为热信号,请分别描述他们的作用与关系:

```
  - (RACMulticastConnection *)publish;
  - (RACMulticastConnection *)multicast:(RACSubject *)subject;
  - (RACSignal *)replay;
  - (RACSignal *)replayLast;
  - (RACSignal *)replayLazily;
```
见美团·点评技术博客
http://tech.meituan.com/talk-about-reactivecocoas-cold-signal-and-hot-signal-part-1.html
http://tech.meituan.com/talk-about-reactivecocoas-cold-signal-and-hot-signal-part-2.html
http://tech.meituan.com/talk-about-reactivecocoas-cold-signal-and-hot-signal-part-3.html

* (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已经释放


















































































































阅读全文
0 0
原创粉丝点击