ReactiveCocoa冷热信号与并发编程

本文深入探讨了ReactiveCocoa中的冷信号与热信号概念,包括它们的区别、应用场景及转换方式。文章还介绍了如何利用RACScheduler进行并发编程,并提供了丰富的示例代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

冷信号与热信号
  • 什么是冷信号与热信号
  • 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 已经释放


















































































































评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值