preface

Among the articles about cold signal and heat signal in ReactiveCocoa V2.5, the most famous ones are the three articles about cold and heat signal written by Teacher Zang Chengwei of Meituan:

ReactiveCocoa cold signal and hot signal (1) ReactiveCocoa cold signal and hot signal (2) : why to distinguish the cold signal and hot signal ReactiveCocoa cold signal and hot signal (3) : how to deal with the cold signal and hot signal

Since I’ve been writing about the analysis of RACSignal’s underlying implementation, I can’t escape the analysis of hot and cold signal operations. This article intends to analyze the underlying implementation of how to convert a cold signal into a hot signal.

directory

  • 1. Concepts of cold signal and heat signal
  • 2. RACSignal thermal signal
  • 3. RACSignal cold signal
  • 4. How is cold signal converted into heat signal

I. Concepts of cold signal and heat signal

The concept of hot and cold signals is derived from. Hot And Cold Observables in ASP.NET Reactive Extensions(RX)

Hot Observable is active. Even though you don’t subscribe to events, it pushes them all the time, like a mouse movement. A Cold Observable, on the other hand, is passive, publishing messages only when you subscribe.

Hot Observable can have multiple subscribers, one-to-many, and the collection can share information with the subscribers. A Cold Observable, on the other hand, can only be one-to-one. Messages are sent in full when there are different subscribers.

In this paper, the characteristics of ReactiveCocoa cold signal and heat signal (I) are analyzed in detail:

The heat signal is active, even if you don’t subscribe to the event, it’s still being pushed. Cold signals are passive and only send messages when you subscribe.

A thermal signal can have multiple subscribers, one-to-many, and the signal can share information with the subscribers. Cold signals can only be sent one to one, and when there are different subscribers, the message will be sent again in its entirety.

B: RACSignal

There are several signals in the RACSignal family that fit the characteristics of thermal signals.

1.RACSubject



@interface RACSubject : RACSignal <RACSubscriber>

@property (nonatomic.strong.readonly) NSMutableArray *subscribers;
@property (nonatomic.strong.readonly) RACCompoundDisposable *disposable;

- (void)enumerateSubscribersUsingBlock:(void(^) (id<RACSubscriber> subscriber))block;
+ (instancetype)subject;

@endCopy the code

First, let’s look at the definition of RACSubject.

RACSubject is inherited from RACSignal, and it also complies with the RACSubscriber agreement. That means it can send signals as well as subscribe to them.

Inside the RACSubject is an NSMutableArray array that holds all the subscribers of the signal. Then there is the RACCompoundDisposable signal, which holds the RACCompoundDisposable of all subscribers to that signal.

The reason why RACSubject can be called a heat signal is that it must meet the above definition of heat signal. Let’s see how it fits in from its implementation.



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

    RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
    subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];

    NSMutableArray *subscribers = self.subscribers;
    @synchronized (subscribers) {
        [subscribers addObject:subscriber];
    }

    return [RACDisposable disposableWithBlock:^{
        @synchronized (subscribers) {
            NSUInteger index = [subscribers indexOfObjectWithOptions:NSEnumerationReverse passingTest:^ BOOL (id<RACSubscriber> obj, NSUInteger index, BOOL *stop) {
                return obj == subscriber;
            }];

            if(index ! =NSNotFound) [subscribers removeObjectAtIndex:index]; }}]; }Copy the code

Above is the implementation of RACSubject, which differs most from RACSignal in these two lines


NSMutableArray *subscribers = self.subscribers;
@synchronized (subscribers) {
    [subscribers addObject:subscriber];
}Copy the code

RACSubject stores all of its subscribers in an array called NSMutableArray. Now that all subscribers are saved, sendNext, sendError, and sendCompleted need to be changed.



- (void)sendNext:(id)value {
    [self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) {
        [subscriber sendNext:value];
    }];
}

- (void)sendError:(NSError *)error {
    [self.disposable dispose];

    [self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) {
        [subscriber sendError:error];
    }];
}

- (void)sendCompleted {
    [self.disposable dispose];

    [self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) {
        [subscriber sendCompleted];
    }];
}Copy the code

From the source code as you can see, the sendNext RACSubject, sendError, sendCompleted will perform enumerateSubscribersUsingBlock: method.


- (void)enumerateSubscribersUsingBlock:(void(^) (id<RACSubscriber> subscriber))block {
    NSArray *subscribers;
    @synchronized (self.subscribers) {
        subscribers = [self.subscribers copy];
    }

    for (id<RACSubscriber> subscriber insubscribers) { block(subscriber); }}Copy the code

EnumerateSubscribersUsingBlock: method will remove all RACSubject subscribers, in turn, calls the ginseng of block () method.

The subscription and sending process of RACSubject can be referred to in the first article, which is basically the same. The other difference is that it will send signals to its subscribers in turn.

RACSubject meets the characteristics of thermal signal. Even if it has no subscribers, it can send signals by itself because it inherits the RACSubscriber agreement. Cold signals can only be sent if they are subscribed.

RACSubject can have many subscribers, and it saves them all to its own array. The RACSubject then sends the signal, and the subscribers are like watching TV together, losing sight of the broadcast program and receiving the sent signal. Receive the signal. RACSignal sends signals that subscribers can only receive from the beginning, like watching an on-demand show from the beginning every time they watch it.

2. RACGroupedSignal


@interface RACGroupedSignal : RACSubject

@property (nonatomic.readonly.copy) id<NSCopying> key;
+ (instancetype)signalWithKey:(id<NSCopying>)key;
@endCopy the code

First look at the definition of RACGroupedSignal.

RACGroupedSignal is used in the RACsignal method.


- (RACSignal *)groupBy:(id<NSCopying> (^) (id object))keyBlock transform:(id(^) (id object))transformBlockCopy the code

In this method, the last thing inside sendNext is a signal sent by RACGroupedSignal.


[groupSubject sendNext:transformBlock != NULL ? transformBlock(x) : x];Copy the code

See this article for a detailed analysis of groupBy

3. RACBehaviorSubject


@interface RACBehaviorSubject : RACSubject
@property (nonatomic.strong) id currentValue;
+ (instancetype)behaviorSubjectWithDefaultValue:(id)value;
@endCopy the code

The signal stores an object currentValue, which stores the latest value of the signal.

Of course also can call behaviorSubjectWithDefaultValue class method


+ (instancetype)behaviorSubjectWithDefaultValue:(id)value {
    RACBehaviorSubject *subject = [self subject];
    subject.currentValue = value;
    return subject;
}Copy the code

This method stores the default value. If the RACBehaviorSubject doesn’t receive any value, the signal sends the default value.

When RACBehaviorSubject is subscribed:


- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    RACDisposable *subscriptionDisposable = [super subscribe:subscriber];

    RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
        @synchronized (self) {
            [subscriber sendNext:self.currentValue]; }}];return [RACDisposable disposableWithBlock:^{
        [subscriptionDisposable dispose];
        [schedulingDisposable dispose];
    }];
}Copy the code

The currentValue stored in sendNext will always be sent. Calling sendNext calls sendNext in the RACSubject and also sends signal values to each subscriber in the subscription array in turn.

When the RACBehaviorSubject sends sendNext to the subscriber:


- (void)sendNext:(id)value {
    @synchronized (self) {
        self.currentValue = value;
        [supersendNext:value]; }}Copy the code

The RACBehaviorSubject updates the sent value to currentValue. The next time a value is sent, the last updated value is sent.

4. RACReplaySubject



const NSUInteger RACReplaySubjectUnlimitedCapacity = NSUIntegerMax;
@interface RACReplaySubject : RACSubject

@property (nonatomic.assign.readonly) NSUInteger capacity;
@property (nonatomic.strong.readonly) NSMutableArray *valuesReceived;
@property (nonatomic.assign) BOOL hasCompleted;
@property (nonatomic.assign) BOOL hasError;
@property (nonatomic.strong) NSError *error;
+ (instancetype)replaySubjectWithCapacity:(NSUInteger)capacity;

@endCopy the code

The history of RACReplaySubject are stored in RACReplaySubjectUnlimitedCapacity size values.



+ (instancetype)replaySubjectWithCapacity:(NSUInteger)capacity {
    return [(RACReplaySubject *)[self alloc] initWithCapacity:capacity];
}

- (instancetype)init {
    return [self initWithCapacity:RACReplaySubjectUnlimitedCapacity];
}

- (instancetype)initWithCapacity:(NSUInteger)capacity {
    self = [super init];
    if (self= =nil) return nil;

    _capacity = capacity;
    _valuesReceived = (capacity == RACReplaySubjectUnlimitedCapacity ? [NSMutableArray array] : [NSMutableArray arrayWithCapacity:capacity]);

    return self;
}Copy the code

An array of capacity size is initialized during RACReplaySubject initialization.


- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];

    RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
        @synchronized (self) {
            for (id value in self.valuesReceived) {
                if (compoundDisposable.disposed) return;

                [subscriber sendNext:(value == RACTupleNil.tupleNil ? nil : value)];
            }

            if (compoundDisposable.disposed) return;

            if (self.hasCompleted) {
                [subscriber sendCompleted];
            } else if (self.hasError) {
                [subscriber sendError:self.error];
            } else {
                RACDisposable *subscriptionDisposable = [supersubscribe:subscriber]; [compoundDisposable addDisposable:subscriptionDisposable]; }}}]; [compoundDisposable addDisposable:schedulingDisposable];return compoundDisposable;
}Copy the code

When a RACReplaySubject is subscribed, the values in the valuesReceived array are sent.



- (void)sendNext:(id)value {
    @synchronized (self) {[self.valuesReceived addObject:value ? : RACTupleNil.tupleNil]; [super sendNext:value];

        if (self.capacity ! = RACReplaySubjectUnlimitedCapacity &&self.valuesReceived.count > self.capacity) {
            [self.valuesReceived removeObjectsInRange:NSMakeRange(0.self.valuesReceived.count - self.capacity)]; }}}Copy the code

In sendNext, valuesReceived saves the value received each time. Calling sendNext for super sends the values to each subscriber in turn.

It also tells you how many values are stored in the array. If the number of stored values is greater than capacity, remove the first few values starting from 0 to ensure that the array contains only capacity values.

The difference between RACReplaySubject and RACReplaySubject is that RACReplaySubject also stores historical signal values to send to subscribers. At this point, RACReplaySubject is more like RACSingnal and RACSubject combined. RACSignal is the same as RACSignal in that it sends all values to the subscriber once subscribed. But RACReplaySubject also has the property of RACSubject, which sends all values to multiple subscribers. After the RACReplaySubject has sent the previously stored history value, the RACReplaySubject then sends the signal exactly as the RACSubject does.

C: RACSignal

In addition to the RACsignal, there are some special cold signals in ReactiveCocoa V2.5.

1.RACEmptySignal


@interface RACEmptySignal : RACSignal
+ (RACSignal *)empty;
@endCopy the code

This signal has only one empty method.


+ (RACSignal *)empty {
#ifdef DEBUG
    return [[[self alloc] init] setNameWithFormat:@"+empty"];
#else
    static id singleton;
    static dispatch_once_t pred;

    dispatch_once(&pred, ^{
        singleton = [[self alloc] init];
    });

    return singleton;
#endif
}Copy the code

In debug mode, a signal named empty is returned. In Release mode, return a singleton empty signal.


- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    NSCParameterAssert(subscriber ! =nil);
    return [RACScheduler.subscriptionScheduler schedule:^{
        [subscriber sendCompleted];
    }];
}Copy the code

Once subscribed, the RACEmptySignal signal sends sendCompleted.

2. RACReturnSignal



@interface RACReturnSignal : RACSignal
@property (nonatomic.strong.readonly) id value;
+ (RACSignal *)return: (id)value;
@endCopy the code

The RACReturnSignal is also simple to define, returning a RACSignal directly based on the value of value.



+ (RACSignal *)return: (id)value {
#ifndef DEBUG
    if (value == RACUnit.defaultUnit) {
        static RACReturnSignal *unitSingleton;
        static dispatch_once_t unitPred;

        dispatch_once(&unitPred, ^{
            unitSingleton = [[self alloc] init];
            unitSingleton->_value = RACUnit.defaultUnit;
        });

        return unitSingleton;
    } else if (value == nil) {
        static RACReturnSignal *nilSingleton;
        static dispatch_once_t nilPred;

        dispatch_once(&nilPred, ^{
            nilSingleton = [[self alloc] init];
            nilSingleton->_value = nil;
        });

        return nilSingleton;
    }
#endif

    RACReturnSignal *signal = [[self alloc] init];
    signal->_value = value;

#ifdef DEBUG
    [signal setNameWithFormat:@"+return: %@", value];
#endif

    return signal;
}Copy the code

In debug mode, create a RACReturnSignal that stores the value of the input parameter. In release mode, the corresponding singleton RACReturnSignal is created depending on whether the value of value is null.


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

 return [RACScheduler.subscriptionScheduler schedule:^{
    [subscriber sendNext:self.value];
    [subscriber sendCompleted];
 }];
}Copy the code

When the RACReturnSignal is subscribed, it sends only one value and sendCompleted.

3. RACDynamicSignal

This signal is the body that creates RACSignal createSignal:. See the first article for details on the RACDynamicSignal process.

4. RACErrorSignal


@interface RACErrorSignal : RACSignal
@property (nonatomic.strong.readonly) NSError *error;
+ (RACSignal *)error:(NSError *)error;
@endCopy the code

An NSError is stored in the RACErrorSignal.


+ (RACSignal *)error:(NSError *)error {
    RACErrorSignal *signal = [[self alloc] init];
    signal->_error = error;

#ifdef DEBUG
    [signal setNameWithFormat:@"+error: %@", error];
#else
    signal.name = @"+error:";
#endif

    return signal;
}Copy the code

RACErrorSignal is initialized to save the Error passed in. This Error is sent when subscribed.

5. RACChannelTerminal


@interface RACChannelTerminal : RACSignal <RACSubscriber>

- (id)init __attribute__((unavailable("Instantiate a RACChannel instead")));

@property (nonatomic.strong.readonly) RACSignal *values;
@property (nonatomic.strong.readonly) id<RACSubscriber> otherTerminal;
- (id)initWithValues:(RACSignal *)values otherTerminal:(id<RACSubscriber>)otherTerminal;

@endCopy the code

RACChannelTerminal is used for bidirectional binding in RAC daily development. Like RACSubject, it inherits from RACSignal and also complies with the RACSubscriber protocol. Although RACSubject has the characteristics of sending and receiving signals, it is still a cold signal, because it can not one-to-many, it can only send signals one-to-one.

RACChannelTerminal cannot be initialized manually and needs to be initialized by RACChannel.


- (id)init {
    self = [super init];
    if (self= =nil) return nil;

    RACReplaySubject *leadingSubject = [[RACReplaySubject replaySubjectWithCapacity:0] setNameWithFormat:@"leadingSubject"];
    RACReplaySubject *followingSubject = [[RACReplaySubject replaySubjectWithCapacity:1] setNameWithFormat:@"followingSubject"];

    [[leadingSubject ignoreValues] subscribe:followingSubject];
    [[followingSubject ignoreValues] subscribe:leadingSubject];

    _leadingTerminal = [[[RACChannelTerminal alloc] initWithValues:leadingSubject otherTerminal:followingSubject] setNameWithFormat:@"leadingTerminal"];
    _followingTerminal = [[[RACChannelTerminal alloc] initWithValues:followingSubject otherTerminal:leadingSubject] setNameWithFormat:@"followingTerminal"];

    return self;
}Copy the code

The RACChannelTerminal initWithValues: method is called during RACChannel initialization, where the input arguments are of type RACReplaySubject. So when subscribing to the RACChannelTerminal procedure:


- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    return [self.values subscribe:subscriber];
}Copy the code

Self. values is essentially a RACReplaySubject, which is equivalent to subscriblingto a RACReplaySubject. The subscription process is the same as the RACReplaySubject subscription process above.


- (void)sendNext:(id)value {
    [self.otherTerminal sendNext:value];
}

- (void)sendError:(NSError *)error {
    [self.otherTerminal sendError:error];
}

- (void)sendCompleted {
    [self.otherTerminal sendCompleted];
}Copy the code

Self.otherterminal is also of RACReplaySubject type, and the RACChannelTerminal pipe has RACReplaySubject type signals on both sides. When RACChannelTerminal starts sendNext, sendError, and sendCompleted are pipes that call another RACReplaySubject for these corresponding operations.

The usual place to use RACChannelTerminal is on the bidirectional binding between the View and ViewModel.

For example, on the login page, TextField and ViewModel Password are bidirectionally bound



    RACChannelTerminal *passwordTerminal = [_passwordTextField rac_newTextChannel];
    RACChannelTerminal *viewModelPasswordTerminal = RACChannelTo(_viewModel, password);
    [viewModelPasswordTerminal subscribe:passwordTerminal];
    [passwordTerminal subscribe:viewModelPasswordTerminal];Copy the code

Both signals in a bidirectional binding will receive a new signal because of the other’s change.

At this point, all RACSignal classifications are sorted, including cold and hot signals.

Four. How is cold signal converted into heat signal

Why is it sometimes necessary to convert a cold signal into a hot signal? For details, please refer to the examples given in this article: Cold and hot signals of ReactiveCocoa (II) : Why to distinguish hot and cold signals

Based on the RACSignal subscription and sending process, we know that each time we subscribe to a cold RACSignal, a didSubscribe closure is executed. This is where things can go wrong. If RACSignal is used for network requests, then repeated requests will be made in the didSubscribe closure. As mentioned above, the signal is subscribed 6 times, and the network request is also requested 6 times. That’s not what we want. Network requests only need to be made once.

The most important thing about how a signal executes the didSubscribe closure once is that a RACSignal cold signal can only be subscribed once. Since cold signals can only be one-to-one, one-to-many can only be dealt with by heat signals. At this point, you need to convert the cold signal into the heat signal.

In ReactiveCocoa V2.5, the RACMulticastConnection class is used to convert a cold signal to a hot one.



@interface RACMulticastConnection : NSObject
@property (nonatomic.strong.readonly) RACSignal *signal;
- (RACDisposable *)connect;
- (RACSignal *)autoconnect;
@end


@interface RACMulticastConnection(a){
    RACSubject *_signal;
    int32_t volatile _hasConnected;
}
@property (nonatomic.readonly.strong) RACSignal *sourceSignal;
@property (strong) RACSerialDisposable *serialDisposable;
@endCopy the code

Take a look at the definition of the RACMulticastConnection class. The most important is to save two signals, a RACSubject and a sourceSignal(RACSignal type). What is exposed in.h is RACSignal, and what is actually used in.m is RACSubject. You can guess what it does next by looking at its definition: it sends signals with sourceSignal, internally subscribesto sourceSignal with RACSubject, and RACSubject sends the sourceSignal values to its subscribers in turn.

An unfortunate metaphor for RACMulticastConnection is that it is like the “earth” in the center of the image above. The “earth” is the RACSubject that subscribed to sourceSignal, and the RACSubject sends values to various “connectors” (subscribers). SourceSignal has only one subscriber inside the RACSubject, so we fulfill our wish that we only want to execute the didSubscribe closure once, but be able to send the value to each subscriber.

Look at the initialization of RACMulticastConnection


- (id)initWithSourceSignal:(RACSignal *)source subject:(RACSubject *)subject {
    NSCParameterAssert(source ! =nil);
    NSCParameterAssert(subject ! =nil);

    self = [super init];
    if (self= =nil) return nil;

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

    return self;
}Copy the code

The initialization method is to save the RACSignal as sourceSignal and the RACSubject as its signal property.

RACMulticastConnection has two connection methods.



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

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

A function of the emergence of a rare OSAtomicCompareAndSwap32Barrier here, it is the atomic arithmetic operator, is mainly used to Compare and swap, the prototype is as follows:


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

The keyword volatile only ensures that each time a volatile variable is fetched, it is loaded from memory rather than using a value in a register, but it does not guarantee that the code will access the variable correctly.

If we use pseudocode to implement this function:


f (*__theValue == __oldValue) {  
    *__theValue = __newValue;  
    return 1;  
} else {  
    return 0;  
}Copy the code

If _hasConnected 0 means no connection, OSAtomicCompareAndSwap32Barrier returns 1, shouldConnect should be connected. If _hasConnected is 1, meaning that a connection has been made, OSAtomicCompareAndSwap32Barrier returns 0, shouldConnect won’t connect again.

The process of connecting is to subscribe to self.sourcesignal with RACSubject inside RACMulticastConnection. SourceSignal is RACSignal, it saves the RACSubject to RACPassthroughSubscriber, and when sendNext is called RACSubject sendNext, The sourceSignal signal is then sent to each subscriber.


- (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];
}Copy the code

OSAtomicIncrement32Barrier arithmetic operators, and OSAtomicDecrement32Barrier atoms, and 1 + 1 operation. In AutoConnect, to ensure thread-safety, a semaphore like volatile variable for subscriberCount is used to ensure that the first subscriber can connect. The subscriber of the returned new signal subscribes to the RACSubject, which also subscribes to the internal sourceSignal.

There are five ways to convert a cold signal to a hot one, all of which use RACMulticastConnection. The next step is to look at their implementation.

1. multicast:


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

Multicast: initializes a RACMulticastConnection object, where SourceSignal is self and the internal RACSubject is the input subject.


    RACMulticastConnection *connection = [signal multicast:[RACSubject subject]];
    [connection.signal subscribeNext:^(id x) {
        NSLog(@ "% @",x);
    }];
    [connection connect];Copy the code

Call multicast: Converting a cold signal to a hot one has the inconvenience of manually connecting yourself. Note that the converted heat signal is in the Signal property of RACMulticastConnection, so you need to subscribe to connection.signal.

2. publish


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

Publish simply calls the multicast: method, and publish creates a new RACSubject inside and passes it to RACMulticastConnection as an input parameter.


    RACMulticastConnection *connection = [signal publish];
    [connection.signal subscribeNext:^(id x) {
        NSLog(@ "% @",x);
    }];
    [connection connect];Copy the code

The Publish method also calls connect manually.

3. replay


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

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

    return connection.signal;
}Copy the code

The replay method passes RACReplaySubject as RACSubject for RACMulticastConnection, initializes RACMulticastConnection, and automatically calls connect. The returned signal is the converted thermal signal, the RACSubject signal in RACMulticastConnection.

This must be a RACReplaySubject because it was connected first in the replay method. If RACSubject is used, the signal will be sent to each subscriber via RACSubject after connect. The RACReplaySubject is used to save the signal, even if the subscriber subscribed after the connect in the replay method.

4. replayLast


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

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

    return connection.signal;
}Copy the code

The replayLast and replay implementations are basically the same, with the only difference being that the RACReplaySubject passed in has a Capacity of 1, meaning that only the most recent value can be saved. So with replayLast, once you subscribe, you only get the latest value of the original signal.

5. replayLazily


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

The replayLazily implementation is also very similar to the replayLast and replay implementations. We just put connect into the action of defer.

The implementation of the defer operation is as follows:



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

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

The word defer literally means delayed. This is the same as what this function does. The input parameter block() closure is executed only when the new signal returned by defer is subscribed. Subscribers subscribe to the return value of the block() closure, RACSignal.

The block() closure was deferred to create RACSignal, which is what defer is. If the block() closure contains time-dependent operations, or side effects, and you want to defer execution, you can use defer.

There is a similar operation, then


- (RACSignal *)then:(RACSignal * (^)(void))block {
    NSCParameterAssert(block ! =nil);

    return [[[self
        ignoreValues]
        concat:[RACSignal defer:block]]
        setNameWithFormat:@"[%@] -then:".self.name];
}Copy the code

The then operation is also delayed, except that it delays the block() closure until after the original signal has been sent complete. The new signal from the then change does not send any value for as long as the original signal sends the value because ignoreValues, once sendComplete, is followed by the signal generated by the block() closure.

Back to the replayLazily operation, the same function is to convert cold signals to hot signals, except that sourceSignal is subscribed when the new signal returned is subscribed for the first time. The reason is that defer has delayed the execution of the block() closure.

The last

The RACSignal transformation operations remain the higher-order signal operations, which will be analyzed in the next chapter. Finally, please give us more advice.