RACScheduler.m 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. //
  2. // RACScheduler.m
  3. // ReactiveObjC
  4. //
  5. // Created by Josh Abernathy on 4/16/12.
  6. // Copyright (c) 2012 GitHub, Inc. All rights reserved.
  7. //
  8. #import "RACScheduler.h"
  9. #import "RACCompoundDisposable.h"
  10. #import "RACDisposable.h"
  11. #import "RACImmediateScheduler.h"
  12. #import "RACScheduler+Private.h"
  13. #import "RACSubscriptionScheduler.h"
  14. #import "RACTargetQueueScheduler.h"
  15. // The key for the thread-specific current scheduler.
  16. NSString * const RACSchedulerCurrentSchedulerKey = @"RACSchedulerCurrentSchedulerKey";
  17. @interface RACScheduler ()
  18. @property (nonatomic, readonly, copy) NSString *name;
  19. @end
  20. @implementation RACScheduler
  21. #pragma mark NSObject
  22. - (NSString *)description {
  23. return [NSString stringWithFormat:@"<%@: %p> %@", self.class, self, self.name];
  24. }
  25. #pragma mark Initializers
  26. - (instancetype)initWithName:(NSString *)name {
  27. self = [super init];
  28. if (name == nil) {
  29. _name = [NSString stringWithFormat:@"org.reactivecocoa.ReactiveObjC.%@.anonymousScheduler", self.class];
  30. } else {
  31. _name = [name copy];
  32. }
  33. return self;
  34. }
  35. #pragma mark Schedulers
  36. + (RACScheduler *)immediateScheduler {
  37. static dispatch_once_t onceToken;
  38. static RACScheduler *immediateScheduler;
  39. dispatch_once(&onceToken, ^{
  40. immediateScheduler = [[RACImmediateScheduler alloc] init];
  41. });
  42. return immediateScheduler;
  43. }
  44. + (RACScheduler *)mainThreadScheduler {
  45. static dispatch_once_t onceToken;
  46. static RACScheduler *mainThreadScheduler;
  47. dispatch_once(&onceToken, ^{
  48. mainThreadScheduler = [[RACTargetQueueScheduler alloc] initWithName:@"org.reactivecocoa.ReactiveObjC.RACScheduler.mainThreadScheduler" targetQueue:dispatch_get_main_queue()];
  49. });
  50. return mainThreadScheduler;
  51. }
  52. + (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority name:(NSString *)name {
  53. return [[RACTargetQueueScheduler alloc] initWithName:name targetQueue:dispatch_get_global_queue(priority, 0)];
  54. }
  55. + (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority {
  56. return [self schedulerWithPriority:priority name:@"org.reactivecocoa.ReactiveObjC.RACScheduler.backgroundScheduler"];
  57. }
  58. + (RACScheduler *)scheduler {
  59. return [self schedulerWithPriority:RACSchedulerPriorityDefault];
  60. }
  61. + (RACScheduler *)subscriptionScheduler {
  62. static dispatch_once_t onceToken;
  63. static RACScheduler *subscriptionScheduler;
  64. dispatch_once(&onceToken, ^{
  65. subscriptionScheduler = [[RACSubscriptionScheduler alloc] init];
  66. });
  67. return subscriptionScheduler;
  68. }
  69. + (BOOL)isOnMainThread {
  70. return [NSOperationQueue.currentQueue isEqual:NSOperationQueue.mainQueue] || [NSThread isMainThread];
  71. }
  72. + (RACScheduler *)currentScheduler {
  73. RACScheduler *scheduler = NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey];
  74. if (scheduler != nil) return scheduler;
  75. if ([self.class isOnMainThread]) return RACScheduler.mainThreadScheduler;
  76. return nil;
  77. }
  78. #pragma mark Scheduling
  79. - (RACDisposable *)schedule:(void (^)(void))block {
  80. NSCAssert(NO, @"%@ must be implemented by subclasses.", NSStringFromSelector(_cmd));
  81. return nil;
  82. }
  83. - (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block {
  84. NSCAssert(NO, @"%@ must be implemented by subclasses.", NSStringFromSelector(_cmd));
  85. return nil;
  86. }
  87. - (RACDisposable *)afterDelay:(NSTimeInterval)delay schedule:(void (^)(void))block {
  88. return [self after:[NSDate dateWithTimeIntervalSinceNow:delay] schedule:block];
  89. }
  90. - (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block {
  91. NSCAssert(NO, @"%@ must be implemented by subclasses.", NSStringFromSelector(_cmd));
  92. return nil;
  93. }
  94. - (RACDisposable *)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock {
  95. RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
  96. [self scheduleRecursiveBlock:[recursiveBlock copy] addingToDisposable:disposable];
  97. return disposable;
  98. }
  99. - (void)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock addingToDisposable:(RACCompoundDisposable *)disposable {
  100. @autoreleasepool {
  101. RACCompoundDisposable *selfDisposable = [RACCompoundDisposable compoundDisposable];
  102. [disposable addDisposable:selfDisposable];
  103. __weak RACDisposable *weakSelfDisposable = selfDisposable;
  104. RACDisposable *schedulingDisposable = [self schedule:^{
  105. @autoreleasepool {
  106. // At this point, we've been invoked, so our disposable is now useless.
  107. [disposable removeDisposable:weakSelfDisposable];
  108. }
  109. if (disposable.disposed) return;
  110. void (^reallyReschedule)(void) = ^{
  111. if (disposable.disposed) return;
  112. [self scheduleRecursiveBlock:recursiveBlock addingToDisposable:disposable];
  113. };
  114. // Protects the variables below.
  115. //
  116. // This doesn't actually need to be __block qualified, but Clang
  117. // complains otherwise. :C
  118. __block NSLock *lock = [[NSLock alloc] init];
  119. lock.name = [NSString stringWithFormat:@"%@ %s", self, sel_getName(_cmd)];
  120. __block NSUInteger rescheduleCount = 0;
  121. // Set to YES once synchronous execution has finished. Further
  122. // rescheduling should occur immediately (rather than being
  123. // flattened).
  124. __block BOOL rescheduleImmediately = NO;
  125. @autoreleasepool {
  126. recursiveBlock(^{
  127. [lock lock];
  128. BOOL immediate = rescheduleImmediately;
  129. if (!immediate) ++rescheduleCount;
  130. [lock unlock];
  131. if (immediate) reallyReschedule();
  132. });
  133. }
  134. [lock lock];
  135. NSUInteger synchronousCount = rescheduleCount;
  136. rescheduleImmediately = YES;
  137. [lock unlock];
  138. for (NSUInteger i = 0; i < synchronousCount; i++) {
  139. reallyReschedule();
  140. }
  141. }];
  142. [selfDisposable addDisposable:schedulingDisposable];
  143. }
  144. }
  145. - (void)performAsCurrentScheduler:(void (^)(void))block {
  146. NSCParameterAssert(block != NULL);
  147. // If we're using a concurrent queue, we could end up in here concurrently,
  148. // in which case we *don't* want to clear the current scheduler immediately
  149. // after our block is done executing, but only *after* all our concurrent
  150. // invocations are done.
  151. RACScheduler *previousScheduler = RACScheduler.currentScheduler;
  152. NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = self;
  153. @autoreleasepool {
  154. block();
  155. }
  156. if (previousScheduler != nil) {
  157. NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = previousScheduler;
  158. } else {
  159. [NSThread.currentThread.threadDictionary removeObjectForKey:RACSchedulerCurrentSchedulerKey];
  160. }
  161. }
  162. @end