Rate limited API


#1

I’m using MVCS to implement a JSON based API. I’m so glad I worked through this book! I find the store concept quite helpful and the XML parsing, by passing the delegate, to be valuable simplifying tools.
I have a tendency to keep “dumping” things into the store as it’s so convenient - kind of like a big global container (but that’s my problem for now).

The API implementation has some rules, like: no more than 1 request per second.
I’m having to do some pretty weird stuff to make sure requests are separated by a second or more
Currently, all the requests have to line up (synchronize) here and potentially sleep the thread.

- (void)rateLimitAPI { @synchronized(someObjToLockOn) { if ([[self lastAPICall] timeIntervalSinceNow] > -1.0) { [NSThread sleepForTimeInterval:1]; } [self setLastAPICall:[NSDate date]]; } }
Additionally, I need to wait for a return response on some requests, which means the Connection “start” method is using sendSynchronousRequest rather than the async version. So, some of the store’s “fetch” like methods are synchronized also.
Anyone know a more elegant way to put a governor on API requests?
Thanks.


#2

You could enqueue the requests and let a timer-based scheduler service the queued requests, but this will of course introduce additional complexity which you need to manage.


#3

Ibex10, I think maybe you have nudged me in the right direction… Thanks!

I’ve been trying to think through how to add the API requests to a serial queue but I keep running into “check the time and possibly sleep the thread” which I don’t want to do.

Following your suggestion, I could write each API request in a block, add the block to an NSArray, then use the timer to grab the first block in the array and execute it. That should work. Blocks can be added (like an object) to an array, and a 1 second NSTimer may or may not be a performance problem - probably not. I may run into threading issues if the timer is taking a block off the NSArray while other processing is adding blocks to it. :frowning: Would making the NSArray an atomic variable fix that?

I think this is going to make my head hurt, I find block syntax really confusing and haven’t quite got my head around it yet. I guess there is no time like the present!

Apparently, I’m not the only one who finds block syntax annoying, I stumbled onto this very handy reference the other day:
Excuse the vernacular: http://fuckingblocksyntax.com

Also, this article by Adam Preble will be useful: https://www.informit.com/blogs/blog.aspx?uk=Ask-Big-Nerd-Ranch-Blocks-in-Objective-C

Thanks


#4

I wrote some really simplistic code to test whether I’ll run into problems adding blocks to an array and using a timer to pull them off and execute them. One timer pulls blocks out of the array and executes it “block()” and the other timer puts blocks into the array.
Thanks Ibex10 - I think I can make this work for the real API.

This simple test seems to work!
SomeController.h

#import <Cocoa/Cocoa.h> @interface SomeController : NSObject @property (strong) NSMutableArray *blockQueue; @property (nonatomic, strong) NSTimer *queueTimer; @property (nonatomic, strong) NSTimer *addRequestTimer; @end
SomeController.m

[code]#import “SomeController.h”
@interface SomeController ()
{
NSDate *currentTime;
int requestCounter;
}
@end
@implementation SomeController

  • (void)someMethod
    {
    [self setBlockQueue:[NSMutableArray arrayWithObjects:
    [^{ return 0; } copy],
    [^{ return 1; } copy],
    [^{ return 2; } copy],
    [^{ return 3; } copy],
    [^{ return 4; } copy],
    [^{ return 5; } copy],
    [^{ return 6; } copy],
    [^{ return 7; } copy],
    [^{ return 8; } copy],
    [^{ return 9; } copy], nil]];
    requestCounter = 10;

    // start timer to process the queue
    NSTimer *aTimer = [NSTimer timerWithTimeInterval:1
    target:self
    selector:@selector(processTheQueue)
    userInfo:nil
    repeats:YES];
    [self setQueueTimer:aTimer];
    currentTime = [NSDate date];
    [[NSRunLoop mainRunLoop] addTimer:[self queueTimer] forMode:NSRunLoopCommonModes];

    // start a timer to add requests to the queue
    NSTimer *rTimer = [NSTimer timerWithTimeInterval:0.5
    target:self
    selector:@selector(addRequestToQueue)
    userInfo:nil
    repeats:YES];
    [self setAddRequestTimer:rTimer];
    [[NSRunLoop mainRunLoop] addTimer:[self addRequestTimer] forMode:NSRunLoopCommonModes];
    }

  • (void)processTheQueue
    {
    double elapsedTime = [currentTime timeIntervalSinceNow];
    if ([[self blockQueue] count] <= 0) {
    NSLog(@“Elapsed time:%f The queue is empty!!”, elapsedTime);
    } else {
    int (^block)() = [[self blockQueue] objectAtIndex:0];
    NSLog(@“Elapsed time:%f block value:%d”, elapsedTime, block() );
    [[self blockQueue] removeObjectAtIndex:0];
    }
    currentTime = [NSDate date];
    }

  • (void)addRequestToQueue
    {
    [[self blockQueue] addObject:[^{ return requestCounter++; } copy]];
    }
    @end[/code]