123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221 |
- //
- // RACTestScheduler.m
- // ReactiveObjC
- //
- // Created by Justin Spahr-Summers on 2013-07-06.
- // Copyright (c) 2013 GitHub, Inc. All rights reserved.
- //
- #import "RACTestScheduler.h"
- #import <ReactiveObjC/RACEXTScope.h>
- #import "RACCompoundDisposable.h"
- #import "RACDisposable.h"
- #import "RACScheduler+Private.h"
- @interface RACTestSchedulerAction : NSObject
- // The date at which the action should be executed.
- //
- // This absolute time will not actually be honored. This date is only used for
- // comparison, to determine which block should be run _next_.
- @property (nonatomic, copy, readonly) NSDate *date;
- // The scheduled block.
- @property (nonatomic, copy, readonly) void (^block)(void);
- // A disposable for this action.
- //
- // When disposed, the action should not start executing if it hasn't already.
- @property (nonatomic, strong, readonly) RACDisposable *disposable;
- // Initializes a new scheduler action.
- - (instancetype)initWithDate:(NSDate *)date block:(void (^)(void))block;
- @end
- static CFComparisonResult RACCompareScheduledActions(const void *ptr1, const void *ptr2, void *info) {
- RACTestSchedulerAction *action1 = (__bridge id)ptr1;
- RACTestSchedulerAction *action2 = (__bridge id)ptr2;
- return CFDateCompare((__bridge CFDateRef)action1.date, (__bridge CFDateRef)action2.date, NULL);
- }
- static const void *RACRetainScheduledAction(CFAllocatorRef allocator, const void *ptr) {
- return CFRetain(ptr);
- }
- static void RACReleaseScheduledAction(CFAllocatorRef allocator, const void *ptr) {
- CFRelease(ptr);
- }
- @interface RACTestScheduler ()
- // All of the RACTestSchedulerActions that have been enqueued and not yet
- // executed.
- //
- // The minimum value in the heap represents the action to execute next.
- //
- // This property should only be used while synchronized on self.
- @property (nonatomic, assign, readonly) CFBinaryHeapRef scheduledActions;
- // The number of blocks that have been directly enqueued with -schedule: so
- // far.
- //
- // This is used to ensure unique dates when two blocks are enqueued
- // simultaneously.
- //
- // This property should only be used while synchronized on self.
- @property (nonatomic, assign) NSUInteger numberOfDirectlyScheduledBlocks;
- @end
- @implementation RACTestScheduler
- #pragma mark Lifecycle
- - (instancetype)init {
- self = [super initWithName:@"org.reactivecocoa.ReactiveObjC.RACTestScheduler"];
- CFBinaryHeapCallBacks callbacks = (CFBinaryHeapCallBacks){
- .version = 0,
- .retain = &RACRetainScheduledAction,
- .release = &RACReleaseScheduledAction,
- .copyDescription = &CFCopyDescription,
- .compare = &RACCompareScheduledActions
- };
- _scheduledActions = CFBinaryHeapCreate(NULL, 0, &callbacks, NULL);
- return self;
- }
- - (void)dealloc {
- [self stepAll];
- if (_scheduledActions != NULL) {
- CFBridgingRelease(_scheduledActions);
- _scheduledActions = NULL;
- }
- }
- #pragma mark Execution
- - (void)step {
- [self step:1];
- }
- - (void)step:(NSUInteger)ticks {
- @synchronized (self) {
- for (NSUInteger i = 0; i < ticks; i++) {
- const void *actionPtr = NULL;
- if (!CFBinaryHeapGetMinimumIfPresent(self.scheduledActions, &actionPtr)) break;
- RACTestSchedulerAction *action = (__bridge id)actionPtr;
- CFBinaryHeapRemoveMinimumValue(self.scheduledActions);
- if (action.disposable.disposed) continue;
- RACScheduler *previousScheduler = RACScheduler.currentScheduler;
- NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = self;
- action.block();
- if (previousScheduler != nil) {
- NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = previousScheduler;
- } else {
- [NSThread.currentThread.threadDictionary removeObjectForKey:RACSchedulerCurrentSchedulerKey];
- }
- }
- }
- }
- - (void)stepAll {
- [self step:NSUIntegerMax];
- }
- #pragma mark RACScheduler
- - (RACDisposable *)schedule:(void (^)(void))block {
- NSCParameterAssert(block != nil);
- @synchronized (self) {
- NSDate *uniqueDate = [NSDate dateWithTimeIntervalSinceReferenceDate:self.numberOfDirectlyScheduledBlocks];
- self.numberOfDirectlyScheduledBlocks++;
- RACTestSchedulerAction *action = [[RACTestSchedulerAction alloc] initWithDate:uniqueDate block:block];
- CFBinaryHeapAddValue(self.scheduledActions, (__bridge void *)action);
- return action.disposable;
- }
- }
- - (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block {
- NSCParameterAssert(date != nil);
- NSCParameterAssert(block != nil);
- @synchronized (self) {
- RACTestSchedulerAction *action = [[RACTestSchedulerAction alloc] initWithDate:date block:block];
- CFBinaryHeapAddValue(self.scheduledActions, (__bridge void *)action);
- return action.disposable;
- }
- }
- - (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block {
- NSCParameterAssert(date != nil);
- NSCParameterAssert(block != nil);
- NSCParameterAssert(interval >= 0);
- NSCParameterAssert(leeway >= 0);
- RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];
- @weakify(self);
- @synchronized (self) {
- __block RACDisposable *thisDisposable = nil;
- void (^reschedulingBlock)(void) = ^{
- @strongify(self);
- [compoundDisposable removeDisposable:thisDisposable];
- // Schedule the next interval.
- RACDisposable *schedulingDisposable = [self after:[date dateByAddingTimeInterval:interval] repeatingEvery:interval withLeeway:leeway schedule:block];
- [compoundDisposable addDisposable:schedulingDisposable];
- block();
- };
- RACTestSchedulerAction *action = [[RACTestSchedulerAction alloc] initWithDate:date block:reschedulingBlock];
- CFBinaryHeapAddValue(self.scheduledActions, (__bridge void *)action);
- thisDisposable = action.disposable;
- [compoundDisposable addDisposable:thisDisposable];
- }
- return compoundDisposable;
- }
- @end
- @implementation RACTestSchedulerAction
- #pragma mark Lifecycle
- - (instancetype)initWithDate:(NSDate *)date block:(void (^)(void))block {
- NSCParameterAssert(date != nil);
- NSCParameterAssert(block != nil);
- self = [super init];
- _date = [date copy];
- _block = [block copy];
- _disposable = [[RACDisposable alloc] init];
- return self;
- }
- #pragma mark NSObject
- - (NSString *)description {
- return [NSString stringWithFormat:@"<%@: %p>{ date: %@ }", self.class, self, self.date];
- }
- @end
|