1

I need to fetch data from server every 5 minutes. If I do pull down refresh, also need to fetch data from server, and reset the timer.

Below code is the solution now, looks works fine. Just wonder how to simplify the code? Probable there's better way in ReactiveCocoa?

    RACSignal* refreshSignal = [self.refreshControl rac_signalForControlEvents:UIControlEventValueChanged];
    self.timerSignal = [[RACSignal interval:300 onScheduler:[RACScheduler scheduler] withLeeway:2] takeUntil:refreshSignal];
    [self.timerSignal subscribeNext:^(id x) {
        NSLog(@"==========================");
        NSLog(@"[Timer1]");
        [self.viewModel performFetch];
    }];

    [refreshSignal subscribeNext:^(id x) {
        NSLog(@"==========================");
        NSLog(@"[Refresh]");
        [self.viewModel performFetch];
        self.timerSignal = [[RACSignal interval:300 onScheduler:[RACScheduler scheduler] withLeeway:2] takeUntil:refreshSignal];
        [self.timerSignal subscribeNext:^(id x) {
            NSLog(@"==========================");
            NSLog(@"[Timer2]");
            [self.viewModel performFetch];
        }];
    }];
TryinHard
  • 4,078
  • 3
  • 28
  • 54
JerryZhou
  • 4,566
  • 3
  • 37
  • 60

2 Answers2

1

The cleanest way that I can think of would be using a RACReplaySubject, sending interval signals of 300 and then switching to the latest signal sent every time the block is fired.

self.timerSubject = [RACReplaySubject replaySubjectWithCapacity:1];
RACSignal * refreshSignal = [self.refreshControl rac_signalForControlEvents:UIControlEventValueChanged];
RACSignal * timeSignal = [RACSignal interval:300 onScheduler:[RACScheduler scheduler] withLeeway:2];
[self.timerSubject sendNext:timeSignal];

@weakify(self)
[[self.timerSubject.switchToLatest merge:refreshSignal] subscribeNext:^(id _) {
    @strongify(self)
    [self.viewModel performFetch];
}];

[refreshSignal subscribeNext:^(id _) {
    @strongify(self)
    [self.timerSubject sendNext:timeSignal];
}];
Charles Maria
  • 2,165
  • 15
  • 15
  • Yes, your code is much cleaner then mine. Just not familiar with RACReplaySubject. – JerryZhou Aug 21 '15 at 05:55
  • `RACReplaySubject` is a sort-of "manual" signal. It's like the mutable variable equivalent of the functional reactive world. You manually tell it when to send `next`, `error` and `completed`. Since it has a capacity of `1`, it stores the last sent `next` and forwards it to any new subscriptions. – Charles Maria Aug 21 '15 at 06:34
  • 1
    I have tested your code, just one small problem(or may be it's feature): the delay may delay 300 or 301 seconds, and it don't like timer with withLeeway to make the interval offset back; What timer give is if it's 301 seconds, next time it can set back to 209 seconds to make it sable. – JerryZhou Aug 21 '15 at 06:57
  • But your code is good suggestion for me. I just think that 300 on average would be better. Just because the accumulative effect of offset would become huge. – JerryZhou Aug 21 '15 at 07:16
  • Edited to use a timer. – Charles Maria Aug 21 '15 at 07:29
  • I have edit to include a start signal. sendNext:[RACSignal return:nil] – JerryZhou Aug 21 '15 at 11:54
  • And I was never think about sendNext: with a signal before, your answer broaden my horizons. – JerryZhou Aug 21 '15 at 11:59
  • Glad to help, I hope it's good enough to be the accepted answer. – Charles Maria Aug 21 '15 at 12:00
1

I would map the refreshSignal to a timer signal. Every time the refreshSignal sends a value it is mapped to a signal sending a value every five minutes.

As a side note I think this logic belongs in the view model. There the resulting signal could be mapped to a network request with flatMap:. Then an ongoing network request would be cancelled if the user refreshes again quickly.

[[[[refreshSignal startWith:nil]
map:^id(id value) {
    return [[RACSignal interval:5 * 60 onScheduler:[RACScheduler mainThreadScheduler]] startWith:nil];
}]
switchToLatest]
subscribeNext:^(id x) {
    @strongify(self)
    [self.viewModel performFetch];
}];