RAC中将冷信号转为热信号

我在之前一篇文章说到过冷信号带来的问题, 有时候通过将其转为热信号就可以避免, 这篇文章将会介绍如何将一个冷信号转为热信号。

方式1: subscribe

直接上代码吧

RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    [subscriber sendNext:@"signal"];
    return nil;
}];
RACSubject *subject = [RACSubject subject];
[subject subscribeNext:^(id  _Nullable x) {
    NSLog(@"%@",x);
}];
[signal1 subscribe:subject];  

首先RACSubject 是遵守 RACSubscriber 协议的,所以我们可以将 subject 当作 subscriber 传入subscribe方法中。subscripe 的实现如下

- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    NSCParameterAssert(subscriber != nil);

    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;
} 

可以看到 self.didSubscribe(subscriber); 这里 调用了signal 的 didSubscribe 这个block ,并且将 subscriber(也就是subject) 作为参数传入。 看过上篇文章你就会知道,第一段代码中block会被执行,也就是 [subscriber sendNext:@"signal"]; 也会被执行,这里的subscriber其实还是我们创建的subject,这样的话,第一段代码其实可以简化为

RACSubject *subject = [RACSubject subject];
[subject subscribeNext:^(id  _Nullable x) {
    NSLog(@"%@",x);
}];
[subject sendNext:@"signal"];

不过平时我们不会这么去使用,RACSignal+Operationsn 这个类别中提供了更多优良姿势,下面一一介绍。

方式2: multicast

用法如下:

RACSignal *signal1 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    [subscriber sendNext:@"signal1"];
    return nil;
}];
RACSubject *subject1 = [RACSubject subject];
RACMulticastConnection *connection = [signal1 multicast:subject1];
[connection.signal subscribeNext:^(id  _Nullable x) {
    NSLog(@"%@",x);
}];
[connection connect];

首先我们看一下 muticast 的实现

- (RACMulticastConnection *)multicast:(RACSubject *)subject {
    [subject setNameWithFormat:@"[%@] -multicast: %@", self.name, subject.name];
    RACMulticastConnection *connection = [[RACMulticastConnection alloc] initWithSourceSignal:self subject:subject];
    return connection;
}
- (instancetype)initWithSourceSignal:(RACSignal *)source subject:(RACSubject *)subject {
    NSCParameterAssert(source != nil);
    NSCParameterAssert(subject != nil);

    self = [super init];

    _sourceSignal = source;
    _serialDisposable = [[RACSerialDisposable alloc] init];
    _signal = subject;
    
    return self;
}

其实就是生成了一个 RACMulticastConnection 实例,并将 signal(sourceSignal) 和 subject(signal) 分别保存起来。
所以,下面对 connection.signal 的订阅 就是对subject的订阅。

接下来再来看一下 connect 的实现

- (RACDisposable *)connect {
    BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0, 1, &_hasConnected);

    if (shouldConnect) {
        self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal];
    }

    return self.serialDisposable;
}

可以看到 [self.sourceSignal subscribe:_signal]; 这里其实就是 [signal subscripe:subject] 不再多说。
这里有必要说一下 OSAtomicCompareAndSwap32Barrier
原型如下

bool OSAtomicCompareAndSwap32Barrier( int32_t __oldValue, int32_t __newValue, volatile int32_t *__theValue );

如果 __theValue 的值是 __oldValue 该方法会将 __theValue__oldValue 换为 __newValue 并且返回YES,而如果 __theValue 的值已经是 __newValue,则不做交换并且返回NO。因此 connect 方法中 subscribe 方法只会被执行一次。

除了connect之外,RACMulticastConnection 类中还提供了一个autoconnect方法,实现如下

- (RACSignal *)autoconnect {
    __block volatile int32_t subscriberCount = 0;

    return [[RACSignal
        createSignal:^(id<RACSubscriber> subscriber) {
            OSAtomicIncrement32Barrier(&subscriberCount);

            RACDisposable *subscriptionDisposable = [self.signal subscribe:subscriber];
            RACDisposable *connectionDisposable = [self connect];

            return [RACDisposable disposableWithBlock:^{
                [subscriptionDisposable dispose];

                if (OSAtomicDecrement32Barrier(&subscriberCount) == 0) {
                    [connectionDisposable dispose];
                }
            }];
        }]
        setNameWithFormat:@"[%@] -autoconnect", self.signal.name];
}

他会返回一个RACDynamicSignal ,但这个信号被订阅时执行connect方法,因此使用autoconnect的姿势如下:

RACSignal *signal1 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    [subscriber sendNext:@"signal1"];
    return nil;
}];
RACSubject *subject1 = [RACSubject subject];
RACMulticastConnection *connection = [signal1 multicast:subject1];
RACSignal *autoSignal = [connection autoconnect];
[autoSignal subscribeNext:^(id  _Nullable x) {
    NSLog(@"%@",x);
}];

RACMulticastConnection 用于将一个信号的订阅分享给多个订阅者,你需要使用 [RACSignal publish] 或者 [RACSignal multicast:] 来实例RACMulticastConnection对象,并且使用 connectautoconnect 来执行订阅

方式3: publish

在 RACSignal+Operations 这个类别中提供了publish方法来生成RACMulticastConnection示例,实现如下:

- (RACMulticastConnection *)publish {
    RACSubject *subject = [[RACSubject subject] setNameWithFormat:@"[%@] -publish", self.name];
    RACMulticastConnection *connection = [self multicast:subject];
    return connection;
}

该方法只是对 multicast 方法的一个简单封装(在内部创建一个subject并对其multicast),使用 publish 更为方便快捷。使用如下:

RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
     [subscriber sendNext:@"signal1"];
     return nil;
}];
RACMulticastConnection *connection = [signal publish];
[connection.signal subscribeNext:^(id  _Nullable x) {
     NSLog(@"%@",x);
}];//相当于 [subject subscribeNext:...]
[connection connect]; //相当于 [subject sendNext:@"signal1"]

在 RACSignal+Operations 中除了 multicast 与 publish 方法外,还提供了 replay 、replayLast、replayLazily 这三个方法。其实他们都是对 multicast 方法的封装

方式4: replay

实现如下

- (RACSignal *)replay {
    RACReplaySubject *subject = [[RACReplaySubject subject] setNameWithFormat:@"[%@] -replay", self.name];

    RACMulticastConnection *connection = [self multicast:subject];
    [connection connect];

    return connection.signal;
}

publish中操作的是RACSubject并且没有执行connect方法然后返回的是connection实例,而replay是对RACReplaySubject的操作,并且执行了connect方法,返回值是RACReplaySubject的实例。那么我们接受到replay的返回值其实就是拿到了由冷信号转换得到的RACReplaySubject,由于执行过connect方法,所以这个热信号已经进行过sendnext,后续只需要对其订阅即可触发。使用如下:

RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
     [subscriber sendNext:@"signal1"];
     return nil;
}];
RACReplaySubject *rpSubjecxt = [signal replay]; //内部已执行 [rpSubjecxt sendNext:@"signal1"]
[rpSubjecxt subscribeNext:^(id  _Nullable x) {
    NSLog(@"%@",x);
}];

方式5: replayLast

- (RACSignal *)replayLast {
    RACReplaySubject *subject = [[RACReplaySubject replaySubjectWithCapacity:1] setNameWithFormat:@"[%@] -replayLast", self.name];

    RACMulticastConnection *connection = [self multicast:subject];
    [connection connect];

    return connection.signal;
}

可以看到 replayLast 的实现与replay想比只是在对RACReplaySubject的初始化方法,这里将RACReplaySubject中valuesReceived数组的capacity设为1, 所以当这个RACReplaySubject被订阅时只会执行“在他之前的的最后一个” sendNext。 replayLast 顾名思义只会重复订阅之前最后的一条sendNext。

方式6: replayLazily

- (RACSignal *)replayLazily {
    RACMulticastConnection *connection = [self multicast:[RACReplaySubject subject]];
    return [[RACSignal
        defer:^{
            [connection connect];
            return connection.signal;
        }]
        setNameWithFormat:@"[%@] -replayLazily", self.name];
}

和replay不同的是这里将 connect 操作 套在了 defer 方法里面(defer将RACReplaySubject用RACDynamicSignal包裹起来)。

我们来看defer的实现

+ (RACSignal *)defer:(RACSignal<id> * (^)(void))block {
    NSCParameterAssert(block != NULL);

    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        return [block() subscribe:subscriber];
    }] setNameWithFormat:@"+defer:"];
}

只有 defer 方法返回的信号被订阅之后才会执行 return [block() subscribe:subscriber];
return [block() subscribe:subscriber]; 展开就是

[connection connect];
return [connection.signal subscripe:subscriber];

因此 replayLazily 返回的信号只有在被订阅的时候才会执行 [connection.signal subscripe:subscriber], replayLazily 叫做 replayLazily 再合适不过。

replayLazily 的使用如下:

RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        [subscriber sendNext:@"signal1"];
        [subscriber sendNext:@"signal2"];
        return nil;
    }];
RACSignal *rplaSignal = [signal replayLazily]; 
[rplaSignal subscribeNext:^(id  _Nullable x) {
     NSLog(@"%@",x);
}]; // rplaSignal 被订阅时 才会执行内部的 `[connection.signal(RACReplaySubject) subscripe:subscriber]` 操作

总结

  1. 通过冷信号subscribe一个热信号即可通过将热信号当作subscriber(订阅者)来进行sendNext等操作,也就是将冷信号转为了热信号;
  2. multicast就是基于上述原理来实现的;
  3. publish replay replayLast replayLazily 都是基于 multicast 的封装,满足上的不同使用场景。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容