| // |
| // RACScheduler.m |
| // ReactiveCocoa |
| // |
| // Created by Josh Abernathy on 4/16/12. |
| // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| // |
| |
| #import "RACScheduler.h" |
| #import "RACBacktrace.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 |
| |
| - (id)initWithName:(NSString *)name { |
| self = [super init]; |
| if (self == nil) return nil; |
| |
| if (name == nil) { |
| _name = [NSString stringWithFormat:@"com.ReactiveCocoa.%@.anonymousScheduler", self.class]; |
| } else { |
| _name = [name copy]; |
| } |
| |
| return self; |
| } |
| |
| #pragma mark Schedulers |
| |
| + (instancetype)immediateScheduler { |
| static dispatch_once_t onceToken; |
| static RACScheduler *immediateScheduler; |
| dispatch_once(&onceToken, ^{ |
| immediateScheduler = [[RACImmediateScheduler alloc] init]; |
| }); |
| |
| return immediateScheduler; |
| } |
| |
| + (instancetype)mainThreadScheduler { |
| static dispatch_once_t onceToken; |
| static RACScheduler *mainThreadScheduler; |
| dispatch_once(&onceToken, ^{ |
| mainThreadScheduler = [[RACTargetQueueScheduler alloc] initWithName:@"com.ReactiveCocoa.RACScheduler.mainThreadScheduler" targetQueue:dispatch_get_main_queue()]; |
| }); |
| |
| return mainThreadScheduler; |
| } |
| |
| + (instancetype)schedulerWithPriority:(RACSchedulerPriority)priority name:(NSString *)name { |
| return [[RACTargetQueueScheduler alloc] initWithName:name targetQueue:dispatch_get_global_queue(priority, 0)]; |
| } |
| |
| + (instancetype)schedulerWithPriority:(RACSchedulerPriority)priority { |
| return [self schedulerWithPriority:priority name:@"com.ReactiveCocoa.RACScheduler.backgroundScheduler"]; |
| } |
| |
| + (instancetype)scheduler { |
| return [self schedulerWithPriority:RACSchedulerPriorityDefault]; |
| } |
| |
| + (instancetype)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]; |
| } |
| |
| + (instancetype)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]; |
| } |
| } |
| |
| @end |
| |
| @implementation RACScheduler (Deprecated) |
| |
| #pragma clang diagnostic push |
| #pragma clang diagnostic ignored "-Wdeprecated-implementations" |
| |
| + (instancetype)schedulerWithQueue:(dispatch_queue_t)queue name:(NSString *)name { |
| NSCParameterAssert(queue != NULL); |
| |
| return [[RACTargetQueueScheduler alloc] initWithName:name targetQueue:queue]; |
| } |
| |
| #pragma clang diagnostic pop |
| |
| @end |