RACTestScheduler.m 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. //
  2. // RACTestScheduler.m
  3. // ReactiveObjC
  4. //
  5. // Created by Justin Spahr-Summers on 2013-07-06.
  6. // Copyright (c) 2013 GitHub, Inc. All rights reserved.
  7. //
  8. #import "RACTestScheduler.h"
  9. #import <ReactiveObjC/RACEXTScope.h>
  10. #import "RACCompoundDisposable.h"
  11. #import "RACDisposable.h"
  12. #import "RACScheduler+Private.h"
  13. @interface RACTestSchedulerAction : NSObject
  14. // The date at which the action should be executed.
  15. //
  16. // This absolute time will not actually be honored. This date is only used for
  17. // comparison, to determine which block should be run _next_.
  18. @property (nonatomic, copy, readonly) NSDate *date;
  19. // The scheduled block.
  20. @property (nonatomic, copy, readonly) void (^block)(void);
  21. // A disposable for this action.
  22. //
  23. // When disposed, the action should not start executing if it hasn't already.
  24. @property (nonatomic, strong, readonly) RACDisposable *disposable;
  25. // Initializes a new scheduler action.
  26. - (instancetype)initWithDate:(NSDate *)date block:(void (^)(void))block;
  27. @end
  28. static CFComparisonResult RACCompareScheduledActions(const void *ptr1, const void *ptr2, void *info) {
  29. RACTestSchedulerAction *action1 = (__bridge id)ptr1;
  30. RACTestSchedulerAction *action2 = (__bridge id)ptr2;
  31. return CFDateCompare((__bridge CFDateRef)action1.date, (__bridge CFDateRef)action2.date, NULL);
  32. }
  33. static const void *RACRetainScheduledAction(CFAllocatorRef allocator, const void *ptr) {
  34. return CFRetain(ptr);
  35. }
  36. static void RACReleaseScheduledAction(CFAllocatorRef allocator, const void *ptr) {
  37. CFRelease(ptr);
  38. }
  39. @interface RACTestScheduler ()
  40. // All of the RACTestSchedulerActions that have been enqueued and not yet
  41. // executed.
  42. //
  43. // The minimum value in the heap represents the action to execute next.
  44. //
  45. // This property should only be used while synchronized on self.
  46. @property (nonatomic, assign, readonly) CFBinaryHeapRef scheduledActions;
  47. // The number of blocks that have been directly enqueued with -schedule: so
  48. // far.
  49. //
  50. // This is used to ensure unique dates when two blocks are enqueued
  51. // simultaneously.
  52. //
  53. // This property should only be used while synchronized on self.
  54. @property (nonatomic, assign) NSUInteger numberOfDirectlyScheduledBlocks;
  55. @end
  56. @implementation RACTestScheduler
  57. #pragma mark Lifecycle
  58. - (instancetype)init {
  59. self = [super initWithName:@"org.reactivecocoa.ReactiveObjC.RACTestScheduler"];
  60. CFBinaryHeapCallBacks callbacks = (CFBinaryHeapCallBacks){
  61. .version = 0,
  62. .retain = &RACRetainScheduledAction,
  63. .release = &RACReleaseScheduledAction,
  64. .copyDescription = &CFCopyDescription,
  65. .compare = &RACCompareScheduledActions
  66. };
  67. _scheduledActions = CFBinaryHeapCreate(NULL, 0, &callbacks, NULL);
  68. return self;
  69. }
  70. - (void)dealloc {
  71. [self stepAll];
  72. if (_scheduledActions != NULL) {
  73. CFBridgingRelease(_scheduledActions);
  74. _scheduledActions = NULL;
  75. }
  76. }
  77. #pragma mark Execution
  78. - (void)step {
  79. [self step:1];
  80. }
  81. - (void)step:(NSUInteger)ticks {
  82. @synchronized (self) {
  83. for (NSUInteger i = 0; i < ticks; i++) {
  84. const void *actionPtr = NULL;
  85. if (!CFBinaryHeapGetMinimumIfPresent(self.scheduledActions, &actionPtr)) break;
  86. RACTestSchedulerAction *action = (__bridge id)actionPtr;
  87. CFBinaryHeapRemoveMinimumValue(self.scheduledActions);
  88. if (action.disposable.disposed) continue;
  89. RACScheduler *previousScheduler = RACScheduler.currentScheduler;
  90. NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = self;
  91. action.block();
  92. if (previousScheduler != nil) {
  93. NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = previousScheduler;
  94. } else {
  95. [NSThread.currentThread.threadDictionary removeObjectForKey:RACSchedulerCurrentSchedulerKey];
  96. }
  97. }
  98. }
  99. }
  100. - (void)stepAll {
  101. [self step:NSUIntegerMax];
  102. }
  103. #pragma mark RACScheduler
  104. - (RACDisposable *)schedule:(void (^)(void))block {
  105. NSCParameterAssert(block != nil);
  106. @synchronized (self) {
  107. NSDate *uniqueDate = [NSDate dateWithTimeIntervalSinceReferenceDate:self.numberOfDirectlyScheduledBlocks];
  108. self.numberOfDirectlyScheduledBlocks++;
  109. RACTestSchedulerAction *action = [[RACTestSchedulerAction alloc] initWithDate:uniqueDate block:block];
  110. CFBinaryHeapAddValue(self.scheduledActions, (__bridge void *)action);
  111. return action.disposable;
  112. }
  113. }
  114. - (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block {
  115. NSCParameterAssert(date != nil);
  116. NSCParameterAssert(block != nil);
  117. @synchronized (self) {
  118. RACTestSchedulerAction *action = [[RACTestSchedulerAction alloc] initWithDate:date block:block];
  119. CFBinaryHeapAddValue(self.scheduledActions, (__bridge void *)action);
  120. return action.disposable;
  121. }
  122. }
  123. - (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block {
  124. NSCParameterAssert(date != nil);
  125. NSCParameterAssert(block != nil);
  126. NSCParameterAssert(interval >= 0);
  127. NSCParameterAssert(leeway >= 0);
  128. RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];
  129. @weakify(self);
  130. @synchronized (self) {
  131. __block RACDisposable *thisDisposable = nil;
  132. void (^reschedulingBlock)(void) = ^{
  133. @strongify(self);
  134. [compoundDisposable removeDisposable:thisDisposable];
  135. // Schedule the next interval.
  136. RACDisposable *schedulingDisposable = [self after:[date dateByAddingTimeInterval:interval] repeatingEvery:interval withLeeway:leeway schedule:block];
  137. [compoundDisposable addDisposable:schedulingDisposable];
  138. block();
  139. };
  140. RACTestSchedulerAction *action = [[RACTestSchedulerAction alloc] initWithDate:date block:reschedulingBlock];
  141. CFBinaryHeapAddValue(self.scheduledActions, (__bridge void *)action);
  142. thisDisposable = action.disposable;
  143. [compoundDisposable addDisposable:thisDisposable];
  144. }
  145. return compoundDisposable;
  146. }
  147. @end
  148. @implementation RACTestSchedulerAction
  149. #pragma mark Lifecycle
  150. - (instancetype)initWithDate:(NSDate *)date block:(void (^)(void))block {
  151. NSCParameterAssert(date != nil);
  152. NSCParameterAssert(block != nil);
  153. self = [super init];
  154. _date = [date copy];
  155. _block = [block copy];
  156. _disposable = [[RACDisposable alloc] init];
  157. return self;
  158. }
  159. #pragma mark NSObject
  160. - (NSString *)description {
  161. return [NSString stringWithFormat:@"<%@: %p>{ date: %@ }", self.class, self, self.date];
  162. }
  163. @end