美文网首页RAC实战RACRAC实战
RAC中将冷信号转为热信号

RAC中将冷信号转为热信号

作者: hanl001 | 来源:发表于2017-07-15 14:43 被阅读351次

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

    方式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 的封装,满足上的不同使用场景。

    相关文章

      网友评论

        本文标题:RAC中将冷信号转为热信号

        本文链接:https://www.haomeiwen.com/subject/rvaahxtx.html