Skip to content

Instantly share code, notes, and snippets.

@Strilanc
Created January 3, 2014 06:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Strilanc/8233679 to your computer and use it in GitHub Desktop.
Save Strilanc/8233679 to your computer and use it in GitHub Desktop.
A test fake for a reactive cocoa scheduler, allowing manual advancement of time.
// FakeScheduler.h
#import "ReactiveCocoa.h"
@interface FakeScheduler : RACScheduler
-(instancetype) init;
-(void) advanceTimeBy:(NSTimeInterval)duration;
-(NSTimeInterval)time;
-(NSDate*) date;
-(NSDate*) dateAtTimeZero;
@end
// FakeScheduler.m
#import "FakeScheduler.h"
#import "PriorityQueue.h"
@interface PeriodWork : NSObject {
@public NSTimeInterval period;
@public NSTimeInterval next;
@public void(^action)(void);
}
@end
@implementation PeriodWork
@end
@implementation FakeScheduler {
@private PriorityQueue* _queuedWork;
@private NSTimeInterval _elapsedTimeThatHasBeenProcessed;
@private NSTimeInterval _elapsedTimeBeingAdvancedTo;
@private NSObject* _lock;
@private NSDate* _timeZero;
}
-(instancetype) init {
if (self = [super init]) {
self->_lock = [NSObject alloc];
self->_queuedWork = [PriorityQueue priorityQueueWithComparator:^(PeriodWork* obj1, PeriodWork* obj2) {
return [@(obj1->next) compare:@(obj2->next)];
}];
self->_timeZero = NSDate.date;
}
return self;
}
-(NSDate*) dateAtTimeZero {
return _timeZero;
}
-(NSTimeInterval)time {
@synchronized(_lock) {
// using processed time, instead of target time, so that callbacks that check the time see their scheduled time (instead of the far future)
return _elapsedTimeThatHasBeenProcessed;
}
}
-(NSDate*) date {
return [_timeZero dateByAddingTimeInterval:self.time];
}
-(RACDisposable*)afterDelay:(NSTimeInterval)delay schedule:(void(^)(void))block periodElseZero:(NSTimeInterval)periodIfNonZero {
need(block != nil);
need(delay >= 0);
need(delay < INFINITY);
need(periodIfNonZero >= 0);
need(periodIfNonZero < INFINITY);
PeriodWork* p = [PeriodWork new];
p->period = periodIfNonZero;
p->action = block;
@synchronized(_lock) {
p->next = self.time + delay;
[_queuedWork enqueue:p];
}
[self advanceTimeBy:0];
return [RACDisposable disposableWithBlock:^{
@synchronized(self->_lock) {
p->action = nil;
}
}];
}
-(void) advanceTimeBy:(NSTimeInterval)duration {
need(duration >= 0);
need(duration < INFINITY);
@synchronized(_lock) {
_elapsedTimeBeingAdvancedTo += duration;
while (true) {
PeriodWork* p = [_queuedWork tryPeekSmallest];
// no work?
if (p == nil) break;
// work disposed?
if (p->action == nil) {
[_queuedWork tryDequeueSmallest];
continue;
}
// no work for now?
if (p->next > _elapsedTimeBeingAdvancedTo) break;
// advance
[_queuedWork tryDequeueSmallest];
_elapsedTimeThatHasBeenProcessed = p->next;
// reschedule
if (p->period != 0) {
p->next += p->period;
[_queuedWork enqueue:p];
}
// perform scheduled work (note: must be re-entrant safe here)
p->action();
}
_elapsedTimeThatHasBeenProcessed = _elapsedTimeBeingAdvancedTo;
}
}
-(RACDisposable*)afterDelay:(NSTimeInterval)delay schedule:(void(^)(void))block repeatingEvery:(NSTimeInterval)interval {
need(interval != 0);
return [self afterDelay:delay
schedule:block
periodElseZero:interval];
}
-(RACDisposable*)afterDelay:(NSTimeInterval)delay schedule:(void(^)(void))block {
return [self afterDelay:delay
schedule:block
periodElseZero:0];
}
-(RACDisposable*)after:(NSDate *)date schedule:(void (^)(void))block {
need(date != nil);
return [self afterDelay:MAX(0, [date timeIntervalSinceDate:_timeZero])
schedule:block];
}
-(RACDisposable*)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block {
need(date != nil);
return [self afterDelay:MAX(0, [date timeIntervalSinceDate:_timeZero])
schedule:block
repeatingEvery:interval];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment