• I’ve covered it in two previous articlesReactiveCocoaSome of the insights and uses of “The” are not introduced here
  • The first article describes ReactiveCocoa’s use of RACSingle in detail
  • The second article introduces the detailed usage of ReactiveCocoa collection
  • If you are interested, you can read these two articles first
  • Let’s focus on some of themMap.concatAdvanced usage

I. Introduction to common operations of ReactiveCocoa

1. ReactiveCocoa Operation Instructions

  • All the signalsRACSignalBecause all operation methods are defined in racstream.h, you can simply inherit RACStream and have operation handling methods.

2. Operation idea of ReactiveCocoa

  • Hook is a technique used to change the results of API(application programming interface: method) execution.
  • Hook uses: techniques for intercepting API calls.
  • Hook principle: Execute your own method and change the output of the result each time you call an API and return the result

Ii. Advanced operations

1. ReactiveCocoa core method bind

  • ReactiveCocoaThe core method of operation isbindIn RAC, the core development mode is also binding. The previous development mode is assignment. In RAC, the focus should be on binding, which means that when you create an object, you can bind it to what you want to do later, rather than waiting for assignment to do later.
  • The bind method is rarely used in development. Bind belongs to the low-level method in RAC, which has encapsulated many useful methods.
  • The bind method is briefly introduced and used
    • Requirement: Listen for the content of the text box, each time output, after the content of the string"jun"And displayed inlabelon

Method 1: Concatenate the string after the result is returned

    @weakify(self)
    [_textField.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
        @strongify(self)
        self.showLabel.text = [NSString stringWithFormat:@ % @ + % @ "", x, @"jun"];
    }];

Copy the code

Method 2: Before returning the result, concatenate the string, using bind

    [[_textField.rac_textSignal bind:^RACSignalBindBlock _Nonnull{
        return ^RACSignal *(id value, BOOL *stop){
            return [RACReturnSignal return: [NSString stringWithFormat:"Output: %@", value]];
        };
    }] subscribeNext:^(id  _Nullable x) {
        NSLog(@ "% @", x);
    }];
Copy the code
  • bindMethods to introduce
    • bindMethod argument: a return value is passed inRACStreamBindBlocktheblockparameter
    • RACStreamBindBlockIs ablockThe return value is signal, parameter(the value, the stop), so the parameterblockThe return value is also oneblock
    • As follows:
typedef RACSignal * _Nullable (^RACSignalBindBlock)(ValueType _Nullable value, BOOL *stop);

Copy the code
  • RACStreamBindBlock:
    • Parameter one (value): indicates that the original value of the received signal has not been processed
    • Parameters of the two*stop: used to control bindingBlockIf the*stop= yes, then the binding ends.
    • Return value: signal, processed, returned through this signal, generally usedRACReturnSignal, you need to manually import the header fileRACReturnSignal.h
@interface RACReturnSignal<__covariant ValueType> : RACSignal<ValueType>

+ (RACSignal<ValueType> *)return:(ValueType)value;

@end
Copy the code
  • The bind method uses the following steps:
    • Pass in a return valueRACStreamBindBlocktheblock
    • Describe aRACStreamBindBlockThe type ofbindBlockAs ablockThe return value of.
    • Describes a signal that returns a result, asbindBlockThe return value of.
    • Note: inbindBlockTo do signal result processing
  • Bind low-level implementation:
    • The source signal calls bind, which recreates a bind signal.
    • When a binding signal is subscribed, it is called in the binding signaldidSubscribe, generate abindingBlock.
    • When the source signal has something to emit, it passes the content tobindingBlockProcess, callbindingBlock(value,stop)
    • callbindingBlock(value,stop), returns a signal that the content processing is complete(RACReturnSignal).
    • To subscribe toRACReturnSignal, it will get the subscriber of the binding signal and send out the signal content after processing

2. The Map (flattenMap, Map)

  • flattenMap.MapUsed to map source signal content to new content
  • inSwiftSee this article for more details about the use of these functionsSwift functional programming of Map&Reduce&Filter

2-1. flattenMap

Maps the contents of the source signal to a new signal, which can be of any type

  • flattenMapUse steps:
    • Pass in ablock.blockThe type is the return valueRACStream, the parametervalue
    • parametervalueIs the content of the source signal, get the content of the source signal to do processing
    • Packaged inRACReturnSignalSignal, go back out
  • flattenMapUnderlying implementation:
    • 0.flattenMapInternal callsbindMethod implementation,flattenMapIn theblockIs returned as the value ofbindIn thebindBlockThe return value of.
    • 1. When subscribing to a binding signal, it is generatedbindBlock.
    • 2. Called when the source signal sends contentbindBlock(value, *stop)
    • 3. CallbindBlock, will be called internallyflattenMaptheblock.flattenMaptheblockFunction: it is to package the processed data into signals
    • 4. The returned signal will eventually act asbindBlockReturn signal, asbindBlockReturn signal of.
    • 5. To subscribe tobindBlockWill get the subscriber of the binding signal, and send out the signal content that has been processed
- (__kindof RACStream *)flattenMap:(__kindof RACStream * (^)(id value))block {
	Class class = self.class;

	return [[self bind:^{
		return^ (id value, BOOL *stop) {
			idstream = block(value) ? : [class empty];
			NSCAssert([stream isKindOfClass:RACStream.class], @"Value returned from -flattenMap: is not a stream: %@", stream);

			return stream;
		};
	}] setNameWithFormat:@"[%@] -flattenMap:".self.name];
}
Copy the code

Simple to use

    @weakify(self)
    [[_textField.rac_textSignal flattenMap:^__kindof RACSignal * _Nullable(NSString * _Nullable value) {
        // This block is called when the source signal is emitted.
        // Return value: bind the contents of the signal.
        return [RACReturnSignal return: [NSString stringWithFormat:"Flat output: %@", value]];
    }] subscribeNext:^(id  _Nullable x) {
        @strongify(self)
        // Subscribe to the binding signal. Every time the original signal sends content, after processing, this black will be called
        self.showLabel.text = x;
        NSLog(@ "% @", x);
    }];
Copy the code

2-2. Map

Map function: maps the value of the source signal to a new value

  • MapUse steps:
    • Pass in ablock, type is return object, parameter isvalue
    • valueIs the content of the source signal, directly get the content of the source signal to do processing
    • I’m just going to return it, instead of wrapping it up as a signal, and the value returned is the value of the mapping.
  • MapUnderlying implementation:
    • MapThe bottom line is actually callingflatternMap, a Map ofblockThe value returned by theflatternMapBlock in.
    • When the subscription binding signal is generatedbindBlock.
    • Called when the source signal sends the contentbindBlock(value, *stop)
    • callbindBlock, will be called internallyflattenMaptheblock
    • flattenMaptheblockInternally it callsMapIn theblock, put the MapblockThe returned content is wrapped as the returned signal.
    • The returned signal will eventually act asbindBlockReturn signal, asbindBlockReturn signal of.
    • To subscribe tobindBlockWill get the subscriber of the binding signal, and send out the signal content that has been processed
- (__kindof RACStream *)map:(id(^) (id value))block {
	NSCParameterAssert(block ! =nil);

	Class class = self.class;
	
	return [[self flattenMap:^(id value) {
		return [class return:block(value)];
	}] setNameWithFormat:@"[%@] -map:".self.name];
}
Copy the code

Simple to use

    //Map
    [[_textField.rac_textSignal map:^id _Nullable(NSString * _Nullable value) {
        return [NSString stringWithFormat:@"map output: %@", value];
    }] subscribeNext:^(id  _Nullable x) {
        @strongify(self)
        self.showLabel.text = x;
        NSLog(@ "% @", x);
    }];
    
    // Array processing
    NSArray *arr = @[@ "2".@ "3".@"a".@"g"];
    RACSequence *sequence = [arr.rac_sequence map:^id _Nullable(id  _Nullable value) {
        return [NSString stringWithFormat:@ % @ - "", value];
    }];
    
    NSLog(@ "% @", [sequence array]);
    
    / * output: 2018-03-24 14:13:32. 421337 + 0800 ReactiveObjc [9043-492929] (" - 2 - ", "- 3 -", "- a -", "- g -") * /
Copy the code

2-3. FlatternMapMapThe difference between

  • FlatternMapIn theBlockReturn signal.
  • MapIn theBlockReturns an object.
  • In development, if the value emitted by the signal is not a signal, the mapping is generally usedMap
  • In development, if the value emitted by the signal is a signal, the mapping is generally usedFlatternMap
  • Signal of signal
    • When one signal needs to return a value from another signal
    • Let’s take a look at this example
Pragma signals within signals
- (void)singleAndSingle {
    // Create signal within signal
    RACSubject *sonSingle = [RACSubject subject];
    RACSubject *single = [RACSubject subject];
    
    [[sonSingle flattenMap:^__kindof RACSignal * _Nullable(id  _Nullable value) {
        //sonSingle is called when it sends a signal
        return value;
    }] subscribeNext:^(id  _Nullable x) {
        // sonSingle will be called only when sonSingle's child signal is a message
        NSLog("Output: %@", x);
    }];
    
    // A signal within a signal sends a subsignal
    [sonSingle sendNext:single];
    // The subsignal sends the content
    [single sendNext:@123];
}
Copy the code

Combination of 3.

3-1. concat

Concatenation of signals in a fixed sequence. When multiple signals are sent, sequential signals are received

// Let's take a look at the normal operation
- (void)setConcatAction {
    // Execute A first, then B
    RACSubject *subjectA = [RACSubject subject];
    RACSubject *subjectB = [RACReplaySubject subject];
    
    NSMutableArray *array = [NSMutableArray array];
    
    // Subscribe signal
    [subjectA subscribeNext:^(id  _Nullable x) {
        [array addObject:x];
    }];
    [subjectB subscribeNext:^(id  _Nullable x) {
        [array addObject:x];
    }];
    
    // Send a signal
    [subjectB sendNext:@"B"];
    [subjectA sendNext:@"A"];
    [subjectA sendCompleted];
    
    // output: [B, A]
    NSLog(@ "% @", array);
}
Copy the code
  • Obviously, the above results do not meet our requirements: restrict A, perform B
  • Let’s look at usageconcatAfter the implementation
- (void)setConcatAction {
    // Execute A first, then B
    RACSubject *subC = [RACSubject subject];
    RACSubject *subD = [RACReplaySubject subject];
    
    NSMutableArray *array2 = [NSMutableArray array];
    
    // Subscribe signal
    [[subC concat:subD] subscribeNext:^(id  _Nullable x) {
        [array2 addObject:x];
    }];
    
    // Send a signal
    [subD sendNext:@"D"];
    [subC sendNext:@"C"];
    [subC sendCompleted];
    
    // output: [C, D]
    NSLog(@ "% @", array2);
}
Copy the code
  • As you can see, the output is as expected, in sequence
  • So,concatHow exactly is the bottom layer implemented?
- (RACSignal *)concat:(RACSignal *)signal {
	return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
		RACCompoundDisposable *compoundDisposable = [[RACCompoundDisposable alloc] init];

		RACDisposable *sourceDisposable = [self subscribeNext:^(id x) {
			[subscriber sendNext:x];
		} error:^(NSError *error) {
			[subscriber sendError:error];
		} completed:^{
			RACDisposable *concattedDisposable = [signal subscribe:subscriber];
			[compoundDisposable addDisposable:concattedDisposable];
		}];

		[compoundDisposable addDisposable:sourceDisposable];
		return compoundDisposable;
	}] setNameWithFormat:@"[%@] -concat: %@".self.name, signal];
}
Copy the code
  • concatUnderlying implementation:
    • When a concatenation signal is subscribed, the concatenation signal is calleddidSubscribe
    • didSubscribeThe first source signal is subscribed tosubjectA
    • The first source signal is executedsubjectAthedidSubscribe
    • The first source signalsubjectAthedidSubscribe, the first source signal is calledsubjectAThe subscriber’snextBlockThe value is sent out by the subscriber of the concatenated signal.
    • The first source signalsubjectAthedidSubscribeThe first source signal is called when thesubjectAThe subscriber’scompletedBlockSubscribe to the second source signalsubjectBThat’s when it’s activatedsubjectB
    • Subscribe to the second source signalsubjectBExecute the second source messagesubjectBThe no.didSubscribe
    • Second source signalsubjectBthedidSubscribeThe value is sent through the subscriber of the concatenated signal.

3-2. then

Used to join two signals. When the first signal is complete, the signal returned by then is joined

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

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

//ignoreValues underlying implementation
- (RACSignal *)ignoreValues {
	return [[self filter:^(id _) {
		return NO;
	}] setNameWithFormat:@"[%@] -ignoreValues".self.name];
}
Copy the code
  • Realize the principle of
    • The underlying callsfilterFilter out the values emitted by its own signal (filterMore on that later.)
    • And then use itconcatThe connectionthenReturned signal
    • Here are the test cases
- (void)setThenAction {
    RACSubject *subjectA = [RACReplaySubject subject];
    RACSubject *subjectB = [RACReplaySubject subject];
    
    // Send a signal
    [subjectA sendNext:@"A"];
    [subjectA sendCompleted];
    [subjectB sendNext:@"B"];
    
    // Subscribe signal
    [[subjectA then:^RACSignal * _Nonnull{
        return subjectB;
    }] subscribeNext:^(id  _Nullable x) {
        NSLog(@ "% @", x);
    }];
    
    // only: B will be printed
    // Will not output: A
}
Copy the code

3-3. merge

Merges multiple signals into a single signal, which is called when any signal has a new value

- (RACSignal *)merge:(RACSignal *)signal {
	return [[RACSignal
		merge:@[ self, signal ]]
		setNameWithFormat:@"[%@] -merge: %@".self.name, signal];
}

+ (RACSignal *)merge:(id<NSFastEnumeration>)signals {
	NSMutableArray *copiedSignals = [[NSMutableArray alloc] init];
	for (RACSignal *signal in signals) {
		[copiedSignals addObject:signal];
	}

	return [[[RACSignal
		createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
			for (RACSignal *signal in copiedSignals) {
				[subscriber sendNext:signal];
			}

			[subscriber sendCompleted];
			return nil;
		}]
		flatten]
		setNameWithFormat:@"+merge: %@", copiedSignals];
}
Copy the code
  • The underlying implementation
    • 1. When the merge signal is subscribed, all the signals are traversed and emitted.
    • 2. Every time a signal is sent, it will be subscribed
    • 3. Once a merge signal is subscribed, all the signals in it will be subscribed.
    • 4. As soon as a signal is sent, it will be monitored.
- (void)setMergeAction {
    // Just want to disordered integration of signal data
    RACSubject *subjectA = [RACSubject subject];
    RACSubject *subjectB = [RACSubject subject];
    RACSubject *subjectC = [RACSubject subject];

    // Merge signals
    RACSignal *single = [[subjectA merge:subjectB] merge:subjectC];
    
    // Subscribe signal
    [single subscribeNext:^(id  _Nullable x) {
        NSLog(@ "% @", x);
    }];
    
    // Send a message
    [subjectA sendNext:@"A"];
    [subjectC sendNext:@"C"];
    [subjectB sendNext:@"B"];
}

// Output A, C, B
Copy the code

3-4. zipWith

Compressing two signals into one will trigger the next event of the compressed stream only if both signals emit signal contents at the same time and the contents of the two signals are combined into a tuple

  • Underlying implementation:
    • 1. Define the compressed signal and the internal will automatically subscribesubjectA.subjectB
    • 2. WheneversubjectAorsubjectBGive a signal, and you will judgesubjectA.subjectBIf a signal is sent, it will pack the most recent signal into a tuple.
- (void)setZipwithAction {
    // Just want to disordered integration of signal data
    RACSubject *subjectA = [RACSubject subject];
    RACSubject *subjectB = [RACSubject subject];
    
    // Merge signals
    RACSignal *single = [subjectA zipWith:subjectB];
    
    // Subscribe signal
    [single subscribeNext:^(id  _Nullable x) {
        NSLog(@ "% @", x);
    }];
    
    // Send a message
    [subjectA sendNext:@"A"];
    [subjectB sendNext:@"B"];
    
    (A, B) */
}
Copy the code

3-5. combineLatest

  • Combine multiple signals and get the latest values for each signal
  • Each merged signal must be at least oncesendNext, will trigger the merge signal
  • Here we consider the requirement that, on the login page, the login button can only be clicked if both the account and password are entered, otherwise it cannot be clicked
  • Normally we want to listen for input from each text box
  • So let’s seecombineLatestControls whether the login button is clickable
- (void)setCombineLatest {
    // Combine two signals into a single signal, just like zip
    RACSignal *single = [_accountText.rac_textSignal combineLatestWith:_passwordText.rac_textSignal];
    
    [single subscribeNext:^(id  _Nullable x) {
        RACTupleUnpack(NSString *account, NSString *password) = x;
        
        _loginButton.enabled = account.length > 0 && password.length > 0;
    }];
}
Copy the code
  • Underlying implementation:
    • 1. When the combination signal is subscribed, the internal will automatically subscribe to two signals, both signals must send content, will be triggered. (Zip, on the other hand, is triggered only when two signals are sent out.)
    • 2. Combine the two signals into a tuple.

3-6. reduce

Aggregation: Used for signaling tuples, aggregating the values of signaling tuples into one value

Here we optimize the above code using a RACSingle class method

- (void)setReduceAction {
    // reduce: aggregates the values of multiple signals into a single value
    RACSignal *single = [RACSignal combineLatest:@[_accountText.rac_textSignal, _passwordText.rac_textSignal] reduce:^id (NSString *account, NSString *password){
        return @(account.length > 0 && password.length > 0);
    }];
    
    [single subscribeNext:^(id  _Nullable x) {
        _loginButton.enabled = [x boolValue];
    }];
}
Copy the code
  • RACSingleClass method
    • A parameter:(id<NSFastEnumeration>)type
      • NSFastEnumerationIn our last articleReactiveCocoa collection usage details 02A brief introduction to the
      • NSFastEnumeration: is a protocol that all classes that follow this protocol can be treated as an array, for exampleNSArray
      • So here, you should pass an includeRACSingleArray of signals
    • Parameters of the two:(RACGenericReduceBlock)reduceBlockIs ablack
typedef ValueType _Nonnull (^RACGenericReduceBlock)();

// ReduceBLCOK parameters, how many signal combinations, reduceBLcoK has how many parameters, each parameter is the previous signal sent content
Copy the code

I’m going to use a macro here, and I really need to simplify this code

- (void)setReduceAction {

    RAC(_loginButton, enabled) = [RACSignal combineLatest:@[_accountText.rac_textSignal, _passwordText.rac_textSignal] reduce:^id (NSString *account, NSString *password){
        return @(account.length > 0 && password.length > 0);
    }];
}
Copy the code

Here in a macro RAC, temporarily not go into, will focus on organizing your RAC macros, concrete implementation is as follows

#define RAC(TARGET, ...) \
    metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \
        (RAC_(TARGET, __VA_ARGS__, nil)) \
        (RAC_(TARGET, __VA_ARGS__))

/// Do not use this directly. Use the RAC macro above.
#define RAC_(TARGET, KEYPATH, NILVALUE) \
    [[RACSubscriptingAssignmentTrampoline alloc] initWithTarget:(TARGET) nilValue:(NILVALUE)][@keypath(TARGET, KEYPATH)]
Copy the code

4. The filter

4-1. filter

Filter signals. Filter out signals that do not meet the conditions

- (void) filterAction{
    //filter
    // Intercepts characters equal to 11 bits
    [[_accountText.rac_textSignal filter:^BOOL(NSString * _Nullable value) {
        // For cell phone numbers, return true only if the number equals 11 digits
        return value.length == 11;
    }]subscribeNext:^(NSString * _Nullable x) {
        // Only characters equal to 11 bits are returned
        NSLog(@"filter = %@", x);
    }];
}
Copy the code

The underlying filter is the flatMap method called as follows:

- (__kindof RACStream *)filter:(BOOL(^) (id value))block {
	NSCParameterAssert(block ! =nil);

	Class class = self.class;
	
	return [[self flattenMap:^ id (id value) {
		if (block(value)) {
			return [class return:value];
		} else {
			return class.empty;
		}
	}] setNameWithFormat:@"[%@] -filter:".self.name];
}
Copy the code

4-2. ignore

A signal that ignores certain values

- (void)setIgnoreAction {
    ///ignore
    // Only the first character bit: m can be tested
    [[_accountText.rac_textSignal ignore:@"m"] subscribeNext:^(NSString * _Nullable x) {
        NSLog(@"ignore = %@", x);
    }];
    
    //ignoreValues: Ignore all signals
    [[_passwordText.rac_textSignal ignoreValues] subscribeNext:^(id  _Nullable x) {
        NSLog(@"allIgnore = %@", x);
    }];
}
Copy the code

At the bottom of the ignore method are the filter methods that are called

//ignore
- (__kindof RACStream *)ignore:(id)value {
	return [[self filter:^ BOOL (id innerValue) {
		returninnerValue ! = value && ! [innerValue isEqual:value]; }] setNameWithFormat:@"[%@] -ignore: %@".self.name, RACDescription(value)];
}

//ignoreValues
- (RACSignal *)ignoreValues {
	return [[self filter:^(id _) {
		return NO;
	}] setNameWithFormat:@"[%@] -ignoreValues".self.name];
}
Copy the code

4-3. distinctUntilChanged

  • Signals are emitted when there is a significant change between the previous value and the current value, otherwise it will be ignored.
  • In development, refreshing the UI is used frequently, only twice when the data is different
//distinctUntilChanged
- (void)setdistinctUntilChanged {
    // Create a signal
    RACSubject *subject = [RACSubject subject];

    / / subscribe
    [[subject distinctUntilChanged] subscribeNext:^(id  _Nullable x) {
        NSLog(@"distinctUntilChanged = %@", x);
    }];
    
    [subject sendNext:@12];
    [subject sendNext:@12];
    [subject sendNext:@23];
    
    DistinctUntilChanged = 12 distinctUntilChanged = 23 */
}
Copy the code

DistinctUntilChanged underneath is a high-level use of bind for the call

- (__kindof RACStream *)distinctUntilChanged {
	Class class = self.class;

	return [[self bind:^{
		__block id lastValue = nil;
		__block BOOL initial = YES;

		return^ (id x, BOOL *stop) {
			if(! initial && (lastValue == x || [x isEqual:lastValue]))return [class empty];

			initial = NO;
			lastValue = x;
			return [class return:x];
		};
	}] setNameWithFormat:@"[%@] -distinctUntilChanged".self.name];
}
Copy the code

4-4. take

The sendCompleted statement stops sending signals prematurely when it encounters the sendCompleted statement execution

- (void)setTakeAndTakeLast {
    //take
    RACSubject *subject1 = [RACSubject subject];
    [[subject1 take:2] subscribeNext:^(id  _Nullable x) {
        NSLog(@ "% @", x);
    }];
    
    [subject1 sendNext:@1];
    [subject1 sendNext:@2];
    [subject1 sendCompleted];
    [subject1 sendNext:@3];
    
    // Outputs: 1, 2 respectively
}

// If the code above sends the signal is adjusted to
    [subject1 sendNext:@1];
    [subject1 sendCompleted];
    [subject1 sendNext:@2];
    [subject1 sendNext:@3];
    
    // Then the output will be: 1
Copy the code

4-5. takeLast

Get N signals before calling sendCompleted, provided that the subscriber must call sendCompleted or nothing will be done

- (void)setTakeAndTakeLast {
    //takeLast
    RACSubject *subject1 = [RACSubject subject];
    [[subject1 takeLast:2] subscribeNext:^(id  _Nullable x) {
        NSLog(@ "% @", x);
    }];
    
    [subject1 sendNext:@1];
    [subject1 sendNext:@2];
    [subject1 sendNext:@3];
    [subject1 sendCompleted];
}
Copy the code

4-6. takeUntil

Once the incoming signal has finished sending or the subject2 has started sending the signal, the signal content will not be received

- (void)setTakeAndTakeLast {
    //takeUntil
    RACSubject *subject1 = [RACSubject subject];
    RACSubject *subject2 = [RACSubject subject];
    
    [[subject1 takeUntil:subject2] subscribeNext:^(id  _Nullable x) {
        NSLog(@ "% @", x);
    }];
    
    [subject1 sendNext:@11];
    [subject1 sendNext:@12];
// [subject1 sendCompleted];
    [subject1 sendNext:@13];
    [subject2 sendNext:@ "21"];
    [subject2 sendNext:@ "22"];
    
    // Output: 11, 12, 13
    // When sendCompleted is uncommented, only: 11, 12 is printed
}
Copy the code

4-7. switchToLatest

  • A signal that is used primarily for signals, and sometimes emits signals, will pick up the latest signal sent in the signal’s signal
  • The underlying method is calledflattenMapmethods
- (RACSignal *)switchToLatest {
	return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
		RACMulticastConnection *connection = [self publish];

		RACDisposable *subscriptionDisposable = [[connection.signal
			flattenMap:^(RACSignal *x) {
				NSCAssert(x == nil || [x isKindOfClass:RACSignal.class], @"-switchToLatest requires that the source signal (%@) send signals. Instead we got: %@".self, x);

				// -concat:[RACSignal never] prevents completion of the receiver from
				// prematurely terminating the inner signal.
				return [x takeUntil:[connection.signal concat:[RACSignal never]]];
			}]
			subscribe:subscriber];

		RACDisposable *connectionDisposable = [connection connect];
		return [RACDisposable disposableWithBlock:^{
			[subscriptionDisposable dispose];
			[connectionDisposable dispose];
		}];
	}] setNameWithFormat:@"[%@] -switchToLatest".self.name];
}
Copy the code

Let’s take a look at an example

- (void)setswitchToLatest {
    // The signal of the signal
    RACSubject *subject1 = [RACSubject subject];
    RACSubject *subject2 = [RACSubject subject];

    // Get the latest signal in signal, subscribe to the latest signal
    [[subject1 switchToLatest] subscribeNext:^(id  _Nullable x) {
        NSLog(@ "% @", x);
    }];
    
    // Send a signal
    [subject1 sendNext:subject2];
    [subject2 sendNext:@" Signal of signal"];
    
    // Final result output: "signal of signal"
}
Copy the code

4-8. skip

Skip N signals before subscribing to signals

- (void)setSkipAction {
    // Create a signal
    RACSubject *subject = [RACSubject subject];
    
    // Subscribe signal
    // Skip two signals
    [[subject skip:2] subscribeNext:^(id  _Nullable x) {
        NSLog(@ "% @", x);
    }];
    
    // Send a signal
    [subject sendNext:@1];
    [subject sendNext:@2];
    [subject sendNext:@3];
    [subject sendNext:@4];
    
    // Because two signals are skipped above, only 3, 4 are printed here
}
Copy the code

5. Perform scheduled operations

5-1. interval

A timer that sends out signals at regular intervals

    // the RAC timer is executed at intervals
    [[RACSignal interval:1 onScheduler:[RACScheduler mainThreadScheduler]] subscribeNext:^(NSDate * _Nullable x) {
        NSLog(@timer);
    }];
Copy the code

RACScheduler is a class that manages threads in RAC

5-2. delay

The signal is sent after a delay of some time

    //delay: delay execution
    [[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        [subscriber sendNext:@"delay"];
        return nil;
    }] delay:2] subscribeNext:^(id  _Nullable x) {
        NSLog(@ "% @", x);
    }];
Copy the code

5-3. timeout

Timeout, you can make a signal in a certain time after the automatic error

    //timeout: allows a signal to automatically report an error after a certain period of time
    RACSignal *single = [[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        return nil;
    }] timeout:2 onScheduler:[RACScheduler currentScheduler]];
    
    [single subscribeNext:^(id  _Nullable x) {
        NSLog(@ "% @", x);
    } error:^(NSError * _Nullable error) {
        // Automatically called after 2 seconds
        NSLog(@ "% @", error);
    }];
Copy the code

6. Repeat the operation

6-1. retry

Retry: At any failure, the block in the create signal is re-executed until it succeeds.

- (void)setResertAction {
    //retry
    __block int i = 0;
    [[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        if (i == 5) {
            [subscriber sendNext:@12];
        } else {
            NSLog(@" Error occurred");
            [subscriber sendError:nil];
        }
        i++;
        
        return nil;
    }] retry] subscribeNext:^(id  _Nullable x) {
        NSLog(@ "% @", x);
    } error:^(NSError * _Nullable error) {
        NSLog(@ "% @", error);
    }];
    
    
    2018-03-30 15:44:08.412860+0800 ReactiveObjc[4125:341376] error 2018-03-30 15:44:08.461105+0800 An error occurred in ReactiveObjc[4125:341376] 2018-03-30 15:44:08.461897+0800 ReactiveObjc[4125:341376] 2018-03-30 15:44:08.462478+0800 ReactiveObjc[4125:341376] has an error 2018-03-30 15:44:08.462913+0800 ReactiveObjc[4125:341376] has an error 2018-03-30 15:44:08.463351+0800 ReactiveObjc[4125:341376] 12 */
}
Copy the code

6-2. replay

Replay: When a signal is subscribed multiple times, the content is played repeatedly

    //replay
    RACSignal *single = [[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        [subscriber sendNext:@23];
        [subscriber sendNext:@34];
        
        return nil;
    }] replay];
    
    [single subscribeNext:^(id  _Nullable x) {
        NSLog(@" First subscription -%@", x);
    }];
    
    [single subscribeNext:^(id  _Nullable x) {
        NSLog(@" Second subscription -%@", x);
    }];
    
    /* Output result: 2018-03-30 15:51:20.115052+0800 ReactiveObjc[4269:361568] first subscription -23 2018-03-30 15:51:20.115195+0800 ReactiveObjc[4269:361568] First subscription -34 2018-03-30 15:51:20.115278+0800 ReactiveObjc[4269:361568] second subscription -23 2018-03-30 15:51:20.115352+0800 ReactiveObjc[4269:361568] Second subscription -34 */
Copy the code

6-3. throttle

Throttling: When a signal is sent frequently, the throttling can be used. After a certain period of time (1 second), no signal content is received. After this period (1 second), the last signal content is sent.

    RACSubject *subject = [RACSubject subject];
    
    [[subject throttle:0.001] subscribeNext:^(id  _Nullable x) {
        NSLog(@ "% @", x);
    }];
    
    [subject sendNext:@10];
    [subject sendNext:@11];
    [subject sendNext:@12];
    [subject sendNext:@13];
    [subject sendNext:@14];
    [subject sendNext:@15];
    [subject sendNext:@16];
    [subject sendNext:@17];
    [subject sendNext:@18];
    
    // The execution speed is very fast, so there is only one final output: 18
Copy the code
  • These are some examples and explanations of common advanced usage in RAC
  • If there are shortcomings, please give me more advice, and we will continue to update the relevant knowledge points in the later period
  • Here are two articles on RAC
    • ReactiveCocoa Usage details 01
    • ReactiveCocoa collection usage details 02