// // RACScheduler.m // ReactiveObjC // // Created by Josh Abernathy on 4/16/12. // Copyright (c) 2012 GitHub, Inc. All rights reserved. // #import "RACScheduler.h" #import "RACCompoundDisposable.h" #import "RACDisposable.h" #import "RACImmediateScheduler.h" #import "RACScheduler+Private.h" #import "RACSubscriptionScheduler.h" #import "RACTargetQueueScheduler.h" // The key for the thread-specific current scheduler. NSString * const RACSchedulerCurrentSchedulerKey = @"RACSchedulerCurrentSchedulerKey"; @interface RACScheduler () @property (nonatomic, readonly, copy) NSString *name; @end @implementation RACScheduler #pragma mark NSObject - (NSString *)description { return [NSString stringWithFormat:@"<%@: %p> %@", self.class, self, self.name]; } #pragma mark Initializers - (instancetype)initWithName:(NSString *)name { self = [super init]; if (name == nil) { _name = [NSString stringWithFormat:@"org.reactivecocoa.ReactiveObjC.%@.anonymousScheduler", self.class]; } else { _name = [name copy]; } return self; } #pragma mark Schedulers + (RACScheduler *)immediateScheduler { static dispatch_once_t onceToken; static RACScheduler *immediateScheduler; dispatch_once(&onceToken, ^{ immediateScheduler = [[RACImmediateScheduler alloc] init]; }); return immediateScheduler; } + (RACScheduler *)mainThreadScheduler { static dispatch_once_t onceToken; static RACScheduler *mainThreadScheduler; dispatch_once(&onceToken, ^{ mainThreadScheduler = [[RACTargetQueueScheduler alloc] initWithName:@"org.reactivecocoa.ReactiveObjC.RACScheduler.mainThreadScheduler" targetQueue:dispatch_get_main_queue()]; }); return mainThreadScheduler; } + (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority name:(NSString *)name { return [[RACTargetQueueScheduler alloc] initWithName:name targetQueue:dispatch_get_global_queue(priority, 0)]; } + (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority { return [self schedulerWithPriority:priority name:@"org.reactivecocoa.ReactiveObjC.RACScheduler.backgroundScheduler"]; } + (RACScheduler *)scheduler { return [self schedulerWithPriority:RACSchedulerPriorityDefault]; } + (RACScheduler *)subscriptionScheduler { static dispatch_once_t onceToken; static RACScheduler *subscriptionScheduler; dispatch_once(&onceToken, ^{ subscriptionScheduler = [[RACSubscriptionScheduler alloc] init]; }); return subscriptionScheduler; } + (BOOL)isOnMainThread { return [NSOperationQueue.currentQueue isEqual:NSOperationQueue.mainQueue] || [NSThread isMainThread]; } + (RACScheduler *)currentScheduler { RACScheduler *scheduler = NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey]; if (scheduler != nil) return scheduler; if ([self.class isOnMainThread]) return RACScheduler.mainThreadScheduler; return nil; } #pragma mark Scheduling - (RACDisposable *)schedule:(void (^)(void))block { NSCAssert(NO, @"%@ must be implemented by subclasses.", NSStringFromSelector(_cmd)); return nil; } - (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block { NSCAssert(NO, @"%@ must be implemented by subclasses.", NSStringFromSelector(_cmd)); return nil; } - (RACDisposable *)afterDelay:(NSTimeInterval)delay schedule:(void (^)(void))block { return [self after:[NSDate dateWithTimeIntervalSinceNow:delay] schedule:block]; } - (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block { NSCAssert(NO, @"%@ must be implemented by subclasses.", NSStringFromSelector(_cmd)); return nil; } - (RACDisposable *)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock { RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; [self scheduleRecursiveBlock:[recursiveBlock copy] addingToDisposable:disposable]; return disposable; } - (void)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock addingToDisposable:(RACCompoundDisposable *)disposable { @autoreleasepool { RACCompoundDisposable *selfDisposable = [RACCompoundDisposable compoundDisposable]; [disposable addDisposable:selfDisposable]; __weak RACDisposable *weakSelfDisposable = selfDisposable; RACDisposable *schedulingDisposable = [self schedule:^{ @autoreleasepool { // At this point, we've been invoked, so our disposable is now useless. [disposable removeDisposable:weakSelfDisposable]; } if (disposable.disposed) return; void (^reallyReschedule)(void) = ^{ if (disposable.disposed) return; [self scheduleRecursiveBlock:recursiveBlock addingToDisposable:disposable]; }; // Protects the variables below. // // This doesn't actually need to be __block qualified, but Clang // complains otherwise. :C __block NSLock *lock = [[NSLock alloc] init]; lock.name = [NSString stringWithFormat:@"%@ %s", self, sel_getName(_cmd)]; __block NSUInteger rescheduleCount = 0; // Set to YES once synchronous execution has finished. Further // rescheduling should occur immediately (rather than being // flattened). __block BOOL rescheduleImmediately = NO; @autoreleasepool { recursiveBlock(^{ [lock lock]; BOOL immediate = rescheduleImmediately; if (!immediate) ++rescheduleCount; [lock unlock]; if (immediate) reallyReschedule(); }); } [lock lock]; NSUInteger synchronousCount = rescheduleCount; rescheduleImmediately = YES; [lock unlock]; for (NSUInteger i = 0; i < synchronousCount; i++) { reallyReschedule(); } }]; [selfDisposable addDisposable:schedulingDisposable]; } } - (void)performAsCurrentScheduler:(void (^)(void))block { NSCParameterAssert(block != NULL); // If we're using a concurrent queue, we could end up in here concurrently, // in which case we *don't* want to clear the current scheduler immediately // after our block is done executing, but only *after* all our concurrent // invocations are done. RACScheduler *previousScheduler = RACScheduler.currentScheduler; NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = self; @autoreleasepool { block(); } if (previousScheduler != nil) { NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = previousScheduler; } else { [NSThread.currentThread.threadDictionary removeObjectForKey:RACSchedulerCurrentSchedulerKey]; } } @end