Ian Dundas
Ian Dundas

Reputation: 572

Sample a signal every X seconds, drop all others

I want to sample thestartRangingBeaconsSignal (which fires every ~1 second) every 10 seconds, getting the latest result each time.

From what I understand, this is not the purpose of the throttle function, which actually seems to only sendNext when X amount of time has elapsed without receiving anything.

My attempt below does limit the signal to only sending next once every 10 seconds. However, it doesn't drop interim signals but instead just queues them up to be sent once every 10 seconds. So, long after startRangingBeaconsSignal has finished sending, we're still getting drip-fed results from it.

So in essence, I need a way to receive the latest signal once every 10 seconds, and to ignore all others. Any pointers in the right direction gratefully received.

[[[[BeaconManager sharedInstance] startRangingBeaconsSignal]
        sample:[RACSignal interval:10 onScheduler:[RACScheduler mainThreadScheduler]]]
        subscribeNext:^(NSArray *beacons) {
            // do something with beacons array
        }
    ];

Thanks in advance, and apologies for my dodgy terminology.

Upvotes: 1

Views: 765

Answers (4)

rismay
rismay

Reputation: 271

This is one case where you don't want to throttle to decrease the code complexity. Unfortunately, BTLE is not as efficient as once hoped. Thus, you must make manually handle the start ranging and start ranging calls yourself. The calls to CoreBluetooth still happen and expend battery life. This is equivalent to making extraneous GPS calls every meter and "dropping" them because you only needed every 10 meters. Or polling a server every 0.1 second but only responding to the 10th one.

Upvotes: 0

Ian Dundas
Ian Dundas

Reputation: 572

I finally found that the correct solution is to use sample: and an interval set to how often you want to (surprise surprise) "sample" the signal:

/* countingUpwardsSignal is simply a signal returning x, x+1, x+2, and so on */

NSDate *startDate = [NSDate date];
RACSignal *countingUpwardsSignal = [[[RACSignal interval:1 onScheduler:[RACScheduler mainThreadScheduler]]
    startWith:startDate]
    map:^id (NSDate *newDate) {
        return @((int)[newDate secondsLaterThan:startDate]);
    }];


/* fiveSecondsSampleSignal fires once every five seconds, 
   it is used to gate our subscription to countingUpwardsSignal: */

RACSignal *fiveSecondsSampleSignal = [RACSignal interval:5 onScheduler:[RACScheduler mainThreadScheduler]];

/* we combine the frequency of fiveSecondsSampleSignal to pull 
   the latest value from countingUpwardsSignal: */
[[countingUpwardsSignal sample:fiveSecondsSampleSignal] subscribeNext:^(id x) {
    NSLog (@"x: %@", x);
}];

Which returns:

x: 5
x: 10
x: 15
x: 20
x: 25
x: 30
x: 35
x: 40

which is exactly what I need.

EDIT: actually, as was pointed out, this was just the same as the original code I posted.. duh 😴. The issue with the original code was that when thestartRangingBeaconsSignal stopped firing, sample: would just keep returning the last received "next" over and over, so I was getting duplicates. The solution was to filter based on a third signal isRangingSignal, which would stop the duplicates reaching the subscriber:

@weakify(self);
[[[[[BeaconManager sharedInstance] startRangingBeaconsSignal]
    sample:[RACSignal interval:10 onScheduler:[RACScheduler mainThreadScheduler]]]
    filter:^BOOL (id value) {
        // HERE we check the value of isRangingSignal - if it's stopped, filter NO.
        NSNumber *isRanging = [[BeaconManager sharedInstance] isRangingSignal].first;
        return isRanging.boolValue;
    }]
    subscribeNext:^(NSArray *beacons) {
        NSLog(@"Received beacons: %i", beacons.count);
    }
];

Upvotes: 0

Ash Furrow
Ash Furrow

Reputation: 12421

So throttle: is pretty much what you want. From the documentation:

Sends nexts only if we don't receive another next in interval seconds.

If a next is received, and then another next is received before interval seconds have passed, the first value is discarded.

After interval seconds have passed since the most recent next was sent, the most recent next is forwarded on the scheduler that the value was originally received on. If +[RACScheduler currentScheduler] was nil at the time, a private background scheduler is used.

Returns a signal which sends throttled and delayed next events. Completion and errors are always forwarded immediately.

So you could chain throttle: and distinctUntilChanged to only get the most recent values.

Upvotes: 1

Ash Furrow
Ash Furrow

Reputation: 12421

We're doing something similar here. I'm not sure if this is the best approach, but it does work.

Basically, set up a recurring signal that has a 1-second interval and has take: 10 on it so that it automatically stops. Then do a map on the next events from that signal, ignoring the values you're sent. Inside the map method, return a new signal that represents the work that you want carried out. Then you can just switchToLatest to subscribe to the objects sent by each of those signals.

Upvotes: 1

Related Questions