You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
211 lines
6.3 KiB
211 lines
6.3 KiB
// |
|
// 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
|
|
|