目录
1.RACScheduler是如何封装GCD的
2.RACSequence的一些子类
3.RACScheduler是如何“取消”并发任务的
4.RACScheduler是如何和RAC其他组件进行完美整合的
一. RACScheduler是如何封装GCD的
RACScheduler在ReactiveCocoa中到底是干嘛的呢?处于什么地位呢?
RACScheduler在ReactiveCocoa中是用来控制一个任务,何时何地被执行。它主要是用来解决ReactiveCocoa中并发编程的问题的。RACScheduler的实质是对GCD的封装,底层就是GCD实现的。
要分析RACScheduler,先来回顾一下GCD。
众所周知,在GCD中,Dispatch Queue主要分为2类,Serial Dispatch Queue 和 Concurrent Dispatch Queue 。其中Serial Dispatch Queue是等待现在执行任务处理结束后再派发新任务的队列,Concurrent Dispatch Queue是不等待现在执行任务处理结束就派发新任务的队列。
生成Dispatch Queue的方法也有2种,第一种方式是通过GCD的API生成Dispatch Queue。
dispatch_queue_t serialDispatchQueue = dispatch_queue_create("com.gcd.SerialDispatchQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t concurrentDispatchQueue = dispatch_queue_create("com.gcd.ConcurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
第二种方法是直接获取系统提供的Dispatch Queue。系统提供的也分为2类,Main Dispatch Queue 和 Global Dispatch Queue。Main Dispatch Queue 对应着是Serial Dispatch Queue,Global Dispatch Queue 对应着是Concurrent Dispatch Queue。
Global Dispatch Queue主要分为8种。
首先是以下4种,分别是优先级对应Qos的情况。
- DISPATCH_QUEUE_PRIORITY_HIGH: QOS_CLASS_USER_INITIATED
- DISPATCH_QUEUE_PRIORITY_DEFAULT: QOS_CLASS_DEFAULT
- DISPATCH_QUEUE_PRIORITY_LOW: QOS_CLASS_UTILITY
- DISPATCH_QUEUE_PRIORITY_BACKGROUND: QOS_CLASS_BACKGROUND
其次是,是否支持 overcommit
。加上上面4个优先级,所以一共8种Global Dispatch Queue。带有 overcommit 的队列表示每当有任务提交时,系统都会新开一个线程处理,这样就不会造成某个线程过载(overcommit)。
回到RACScheduler中来,RACScheduler既然是对GCD的封装,那么上述说的这些类型也都有其一一对应的封装。
typedef enum : long {
RACSchedulerPriorityHigh = DISPATCH_QUEUE_PRIORITY_HIGH,
RACSchedulerPriorityDefault = DISPATCH_QUEUE_PRIORITY_DEFAULT,
RACSchedulerPriorityLow = DISPATCH_QUEUE_PRIORITY_LOW,
RACSchedulerPriorityBackground = DISPATCH_QUEUE_PRIORITY_BACKGROUND,
} RACSchedulerPriority;
首先是RACScheduler中的优先级,这里只封装了4种,和GCD中的优先级一一对应。
RACScheduler有6个类方法,都是用来生成一个queue的。
+ (RACScheduler *)immediateScheduler;
+ (RACScheduler *)mainThreadScheduler;
+ (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority name:(NSString *)name;
+ (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority;
+ (RACScheduler *)scheduler;
+ (RACScheduler *)currentScheduler;
接下来依次分析一下它们的底层实现。
1. immediateScheduler
+ (instancetype)immediateScheduler {
static dispatch_once_t onceToken;
static RACScheduler *immediateScheduler;
dispatch_once(&onceToken, ^{
immediateScheduler = [[RACImmediateScheduler alloc] init];
});
return immediateScheduler;
}
immediateScheduler底层实现就是生成了一个RACImmediateScheduler
的单例。
RACImmediateScheduler
是继承自RACScheduler
。
在RACScheduler中,每个种类的RACScheduler都会有一个name
属性,名字也算是他们的标示。RACImmediateScheduler的name是@”com.ReactiveCocoa.RACScheduler.immediateScheduler”
RACImmediateScheduler的作用和它的名字一样,是立即执行闭包里面的任务。
- (RACDisposable *)schedule:(void (^)(void))block {
NSCParameterAssert(block != NULL);
block();
return nil;
}
- (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block {
NSCParameterAssert(date != nil);
NSCParameterAssert(block != NULL);
[NSThread sleepUntilDate:date];
block();
return nil;
}
在schedule:方法中,直接调用执行入参block( )闭包。在after: schedule:方法中,线程先睡眠,直到date的时刻,再醒过来执行入参block( )闭包。
- (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block {
NSCAssert(NO, @"+[RACScheduler immediateScheduler] does not support %@.", NSStringFromSelector(_cmd));
return nil;
}
当然RACImmediateScheduler是不可能支持after: repeatingEvery: withLeeway: schedule:方法的。因为它的定义就是立即执行的,不应该repeat。
- (RACDisposable *)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock {
for (__block NSUInteger remaining = 1; remaining > 0; remaining--) {
recursiveBlock(^{
remaining++;
});
}
return nil;
}
RACImmediateScheduler的scheduleRecursiveBlock:方法中只要recursiveBlock闭包存在,就会无限递归调用执行,除非recursiveBlock不存在了。
2. mainThreadScheduler
mainThreadScheduler也是一个类型是RACTargetQueueScheduler的单例。
+ (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;
}
mainThreadScheduler的名字是@”com.ReactiveCocoa.RACScheduler.mainThreadScheduler”
。
RACTargetQueueScheduler
继承自RACQueueScheduler
@interface RACTargetQueueScheduler : RACQueueScheduler
- (id)initWithName:(NSString *)name targetQueue:(dispatch_queue_t)targetQueue;
@end
在RACTargetQueueScheduler中,只有一个初始化方法。
- (id)initWithName:(NSString *)name targetQueue:(dispatch_queue_t)targetQueue {
NSCParameterAssert(targetQueue != NULL);
if (name == nil) {
name = [NSString stringWithFormat:@"com.ReactiveCocoa.RACTargetQueueScheduler(%s)", dispatch_queue_get_label(targetQueue)];
}
dispatch_queue_t queue = dispatch_queue_create(name.UTF8String, DISPATCH_QUEUE_SERIAL);
if (queue == NULL) return nil;
dispatch_set_target_queue(queue, targetQueue);
return [super initWithName:name queue:queue];
}
先新建了一个queue,name是@”com.ReactiveCocoa.RACScheduler.mainThreadScheduler”
,类型是Serial Dispatch Queue
类型的,然后调用了dispatch_set_target_queue
方法。
所以重点就在dispatch_set_target_queue方法里面了。
dispatch_set_target_queue方法主要有两个目的:
1 设置dispatch_queue_create创建队列的优先级;
2 建立队列的执行阶层。
当使用dispatch_queue_create创建队列的时候,不管是串行还是并行,它们的优先级都是DISPATCH_QUEUE_PRIORITY_DEFAULT级别,而这个API就是可以设置队列的优先级。
举个例子:
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
//注意:被设置优先级的队列是第一个参数。
dispatch_set_target_queue(serialQueue, globalQueue);
通过上面的代码,就把将serailQueue设置成DISPATCH_QUEUE_PRIORITY_HIGH。
使用这个dispatch_set_target_queue方法可以设置队列执行阶层,例如dispatch_set_target_queue(queue, targetQueue);这样设置时,相当于将queue指派给targetQueue,如果targetQueue是串行队列,则queue是串行执行的;如果targetQueue是并行队列,那么queue是并行的。
举个例子:
dispatch_queue_t targetQueue = dispatch_queue_create("targetQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
dispatch_set_target_queue(queue1, targetQueue);
dispatch_set_target_queue(queue2, targetQueue);
dispatch_async(queue1, ^{
NSLog(@"queue1 1");
});
dispatch_async(queue1, ^{
NSLog(@"queue1 2");
});
dispatch_async(queue2, ^{
NSLog(@"queue2 1");
});
dispatch_async(queue2, ^{
NSLog(@"queue2 2");
});
dispatch_async(targetQueue, ^{
NSLog(@"target queue");
});
如果targetQueue为Serial Dispatch Queue,那么输出结果必定如下:
queue1 1
queue1 2
queue2 1
queue2 2
target queue
如果targetQueue为Concurrent Dispatch Queue,那么输出结果可能如下:
queue1 1
queue2 1
queue1 2
target queue
queue2 2
回到RACTargetQueueScheduler中来,在这里传进来的入参是dispatch_get_main_queue( )
,这是一个Serial Dispatch Queue,这里再调用dispatch_set_target_queue
方法,相当于把queue的优先级设置的和main_queue一致。
3. scheduler
以下三个方法实质是同一个方法。
//.h
+ (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority name:(NSString *)name;
+ (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority;
+ (RACScheduler *)scheduler;
//.m
+ (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];
}
通过源码我们能知道,scheduler这一系列的三个方法,是创建了一个 Global Dispatch Queue,对应的属于Concurrent Dispatch Queue。
schedulerWithPriority: name:
方法可以指定线程的优先级和名字。
schedulerWithPriority:
方法只能执行优先级,名字为默认的@”com.ReactiveCocoa.RACScheduler.backgroundScheduler”
。
scheduler
方法创建出来的queue的优先级是默认的,名字也是默认的@”com.ReactiveCocoa.RACScheduler.backgroundScheduler”
。
注意,scheduler和mainThreadScheduler,immediateScheduler这两个单例不同的是,scheduler每次都会创建一个新的Concurrent Dispatch Queue。
4. currentScheduler
+ (instancetype)currentScheduler {
RACScheduler *scheduler = NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey];
if (scheduler != nil) return scheduler;
if ([self.class isOnMainThread]) return RACScheduler.mainThreadScheduler;
return nil;
}
首先,在ReactiveCocoa 中定义了这么一个key,@”RACSchedulerCurrentSchedulerKey”
,这个用来从线程字典里面存取出对应的RACScheduler。
NSString * const RACSchedulerCurrentSchedulerKey = @"RACSchedulerCurrentSchedulerKey";
在currentScheduler这个方法里面看到的是从线程字典里面取出一个RACScheduler。至于什么时候存的,下面会解释到。
如果能从线程字典里面取出一个RACScheduler,就返回取出的RACScheduler。如果字典里面没有,再判断当前的scheduler是否是在主线程上。
+ (BOOL)isOnMainThread {
return [NSOperationQueue.currentQueue isEqual:NSOperationQueue.mainQueue] || [NSThread isMainThread];
}
判断方法如上,只要是NSOperationQueue在mainQueue上,或者NSThread是主线程,都算是在主线程上。
如果是在主线程上,就返回mainThreadScheduler。
如果既不在主线程上,线程字典里面也找不到对应key值对应的value,那么就返回nil。
RACScheduler除了有6个类方法,还有4个实例方法:
- (RACDisposable *)schedule:(void (^)(void))block;
- (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block;
- (RACDisposable *)afterDelay:(NSTimeInterval)delay schedule:(void (^)(void))block;
- (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block;
这4个方法其实从名字上就知道是用来干嘛的。
schedule:
是为RACScheduler添加一个任务,入参是一个闭包。
after: schedule:
是为RACScheduler添加一个定时任务,在date时间之后才执行任务。
afterDelay: schedule:
是为RACScheduler添加一个延时执行的任务,延时delay时间之后才执行任务。
after: repeatingEvery: withLeeway: schedule:
是为RACScheduler添加一个定时任务,在date时间之后才开始执行,然后每隔interval秒执行一次任务。
这四个方法会分别在RACScheduler的各个子类里面进行重写。
比如之前提到的immediateScheduler,schedule:方法中会直接立即执行闭包。after: schedule:方法中添加一个定时任务,在date时间之后才执行任务。after: repeatingEvery: withLeeway: schedule:这个方法在RACImmediateScheduler中就直接返回nil。
还有其他子类在下面会分析这4个方法的实现。
另外还有最后3个方法
- (RACDisposable *)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock;
- (void)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock addingToDisposable:(RACCompoundDisposable *)disposable
- (void)performAsCurrentScheduler:(void (^)(void))block;
前两个方法是实现RACSequence中signalWithScheduler:方法的,具体分析见这篇文章
performAsCurrentScheduler:方法是在RACQueueScheduler中使用到了,在下面子类分析里面详细分析。
网友评论