美文网首页
ReactiveCocoa 中 RACScheduler是如何封

ReactiveCocoa 中 RACScheduler是如何封

作者: woshishui1243 | 来源:发表于2019-01-15 10:18 被阅读6次

    目录

    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)。

    Dispatch Queue.png
    回到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中使用到了,在下面子类分析里面详细分析。

    相关文章

      网友评论

          本文标题:ReactiveCocoa 中 RACScheduler是如何封

          本文链接:https://www.haomeiwen.com/subject/lnsedqtx.html