Tepmnthar Tepmnthar - 1 year ago 54
Objective-C Question

How to satisfy this situation by using ReactiveCocoa

I have a situation where I want to split specific origin signal to two signals, one emitted with no delay and another emitted in 3 seconds delay. And rest of the origin signals always emit a new signal immediately.
But if a new origin signal comes in when the delayed signal of previous origin signal hasn't been emitted, the delayed signal will be discarded.

something like this
enter image description here

I map each of the origin signal to CS1(for immediately emitted signals), and filter specific origin signals to CS2(for delayed emitted signals).
enter image description here
enter image description here

Then I merge CS1 and CS2 into CS3 which will not discard S2.

So, my question is how to discard or cancel S2, and how can I achieve this situation using RAC without using extra temporary variables?

ReactiveCocoa 2.x current code

RACSignal* origin = …
RACSignal* CS1 = [origin map:^id _Nullable(id _Nullable value) {
return @(YES);
}];
RACSignal* CS2 = [[[origin filter:^BOOL(id _Nullable value) {
return [RACSignal empty];
}] delay:3] map:^id _Nullable(id _Nullable value) {
return @(NO);
}];
RACSignal* CS3 = [RACSignal merge:@[CS1, CS2]];

Answer Source

One remark unrelated to the actual question: filter should return a bool that is YES if the element should be sent and NO if the element should be filtered out.

To the actual question:

The solution to the problem is to use takeUntil. However, if you apply takeUntil directly to CS2, CS2 as a whole will be cancelled as soon as an event arrives on CS1.

The solution is to use flatMap, to build a new RACSignal for the delayed element and then use takeUntil on that inner signal.

I've split the single steps into multiple temporary signals only for clarity (I've also changed the map and filter so I could see better whats happening when trying my example, you should easily be able to use your correct functions there):

RACSignal* CS1 = [self.origin map:^id _Nullable(NSNumber * _Nullable value) {
  return value;
}];

RACSignal *filtered = [self.origin filter:^BOOL(NSNumber * _Nullable value) {
  return (value.integerValue % 2) == 0;
}];

RACSignal *delayed = [filtered flattenMap:^__kindof RACSignal * _Nullable(id  _Nullable value) {
  // Build a new signal that returns just this one value,
  // but delayed and only if no event arrives on CS1
  return [[[RACSignal return:value]
           delay:3]
          takeUntil:CS1];
}];

RACSignal* CS2 = [delayed map:^id _Nullable(NSNumber * _Nullable value) {
  return @(-value.integerValue);
}] ;


RACSignal* CS3 = [RACSignal merge:@[CS1, CS2]];

You can easily collapse this back into just two signals

RACSignal *CS2 = [[[self.origin filter:^BOOL(NSNumber * _Nullable value) {
  return (value.integerValue % 2) == 0;
}] flattenMap:^__kindof RACSignal * _Nullable(id  _Nullable value) {
  // Build a new signal that returns just this one value,
  // but delayed and only if no event arrives on CS1 before
  return [[[RACSignal return:value]
           delay:3]
          takeUntil:CS1];
}] map:^id _Nullable(NSNumber * _Nullable value) {
  return @(-value.integerValue);
}];
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download