Tepmnthar
Tepmnthar

Reputation: 557

How to satisfy this situation by using ReactiveCocoa

I have a situation where I want to split events from a specific origin signal into two signals, one emitting events immediately and another emitting events with a 3 seconds delay. And rest of the origin events always emit a new event immediately. But if a new event arrives on the origin signal before the delayed event has been emitted, the delayed event should be discarded.

something like this enter image description here

I map each the origin signal to CS1(for immediately emitted events), and filter specific events of the origin signal to CS2(for delayed emitted events). 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]];

Upvotes: 0

Views: 88

Answers (1)

MeXx
MeXx

Reputation: 3357

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

I've created a sample project on github to demonstrate the solution.

Upvotes: 1

Related Questions