GTLC全球技术领导力峰会·上海站,首批讲师正式上线! 了解详情
写点什么

理解 RACScheduler 的实现

2019 年 12 月 06 日

理解 RACScheduler 的实现

RACScheduler 是一个线性执行队列,ReactiveCocoa 中的信号可以在 RACScheduler 上执行任务、发送结果;它的实现并不复杂,由多个简单的方法和类组成整个 RACScheduler 模块,是整个 ReactiveCocoa 中非常易于理解的部分。


RACScheduler 简介

RACScheduler 作为 ReactiveCocoa 中唯一的用于调度的模块,它包含很多个性化的子类:



RACScheduler 类的内部只有一个用于追踪标记和 debug 的属性 name,头文件和实现文件中的其它内容都是各种各样的方法;我们可以把其中的方法分为两类,一类是用于初始化 RACScheduler 实例的初始化方法:



另一类就是用于调度、执行任务的 +schedule: 等方法:



在图中都省略了一些参数缺省的方法,以及一些调用其他方法的调度方法或者初始化方法,用以减少我们分析和理解整个 RACScheduler 类的难度。


RACScheduler 中,大部分的调度方法都是需要子类覆写,它本身只提供少数的功能,比如递归 block 的执行:


Objective-C


- (RACDisposable *)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock {  RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];  [self scheduleRecursiveBlock:[recursiveBlock copy] addingToDisposable:disposable];  return disposable;}
复制代码


该方法会递归的执行传入的 recursiveBlock,使用的方式非常简单:


Objective-C


[scheduler scheduleRecursiveBlock:^(void (^reschedule)(void)) {    if (needTerminated) return;
// do something
reschedule();}];
复制代码


如果需要递归就执行方法中的 reschedule(),就会再次执行当前的 block;-scheduleRecursiveBlock: 中调用的 -scheduleRecursiveBlock:addingToDisposable: 实现比较复杂:


Objective-C


- (void)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock addingToDisposable:(RACCompoundDisposable *)disposable {    ...    RACDisposable *schedulingDisposable = [self schedule:^{        void (^reallyReschedule)(void) = ^{            [self scheduleRecursiveBlock:recursiveBlock addingToDisposable:disposable];        };
recursiveBlock(^{ reallyReschedule(); }); }]; ...}
复制代码


方法使用了 NSLock 保证在并发情况下并不会出现任何问题,不过在这里展示的代码中,我们将它省略了,一并省略的还有 RACDisposable 相关的代码,以保证整个方法逻辑的清晰,方法的原实现可以查看这里 RACScheduler.m#L130-L187


在每次执行 recursiveBlock 时,都会传入一个 reallyReschedule 用于递归执行传入的 block。


其他的方法包括 +schedule:+after:schedule: 以及 after:repeatingEvery:withLeeway:schedule: 方法都需要子类覆写:


Objective-C


- (RACDisposable *)schedule:(void (^)(void))block;- (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))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;}
复制代码


而接下来我们就按照初始化方法的顺序依次介绍 RACScheduler 的子类了。


RACImmediateScheduler

RACImmediateScheduler 是一个会立即执行传入的代码块的调度器,我们可以使用 RACScheduler 的类方法 +immediateScheduler 返回一个它的实例:


Objective-C


+ (RACScheduler *)immediateScheduler {  static dispatch_once_t onceToken;  static RACScheduler *immediateScheduler;  dispatch_once(&onceToken, ^{    immediateScheduler = [[RACImmediateScheduler alloc] init];  });  return immediateScheduler;}
复制代码


由于 RACImmediateScheduler 是一个私有类,全局只能通过该方法返回它的实例,所以整个程序的运行周期内,我们通过『合法』手段只能获得唯一一个单例。


作为 RACScheduler 的子类,它必须对父类的调度方法进行覆写,不过因为本身的职能原因,RACImmediateScheduler 对于父类的覆写还是非常简单的:


Objective-C


- (RACDisposable *)schedule:(void (^)(void))block {  block();  return nil;}
- (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block { [NSThread sleepUntilDate:date]; block(); return nil;}
- (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;}
复制代码


  • +schedule 方法会立刻执行传入的 block;

  • +after:schedule: 方法会将当前线程休眠到指定时间后执行 block;

  • 而对于 +after:repeatingEvery:withLeeway:schedule: 方法就干脆不支持。


这确实非常符合 RACImmediateScheduler 类的名字以及功能,虽然没有要求对递归执行 block 的方法进行覆写,不过它依然做了这件事情:


Objective-C


- (RACDisposable *)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock {  for (__block NSUInteger remaining = 1; remaining > 0; remaining--) {    recursiveBlock(^{      remaining++;    });  }  return nil;}
复制代码


实现的过程非常简洁,甚至没有什么值得解释的地方了。


RACTargetQueueScheduler

RACTargetQueueScheduler 继承自 RACQueueScheduler,但是由于后者是抽象类,我们并不会直接使用它,它只是为前者提供必要的方法支持,将一部分逻辑抽离出来:



这里我们先简单看一下 RACTargetQueueScheduler 的实现,整个 RACTargetQueueScheduler 类中只有一个初始化方法:


Objective-C


- (instancetype)initWithName:(NSString *)name targetQueue:(dispatch_queue_t)targetQueue {  dispatch_queue_t queue = dispatch_queue_create(name.UTF8String, DISPATCH_QUEUE_SERIAL);  dispatch_set_target_queue(queue, targetQueue);  return [super initWithName:name queue:queue];}
复制代码


初始化方法 -initWithName:targetQueue: 使用 dispatch_queue_create 创建了一个串行队列,然后通过 dispatch_set_target_queue 根据传入的 targetQueue 设置队列的优先级,最后调用父类的指定构造器完成整个初始化过程。


RACTargetQueueScheduler 在使用时,将待执行的任务加入一个私有的串行队列中,其优先级与传入的 targetQueue 完全相同;不过提到 RACTargetQueueScheduler 中队列的优先级,对 GCD 稍有了解的人应该都知道在 GCD 中有着四种不同优先级的全局并行队列,而在 RACScheduler 中也有一一对应的枚举类型:



在使用 +schedulerWithPriority: 方法创建 RACTargetQueueScheduler 时,就需要传入上面的优先级,方法会通过 GCD 的内置方法 dispatch_get_global_queue 获取全局的并行队列,最终返回一个新的实例。


Objective-C


+ (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority name:(NSString *)name {  return [[RACTargetQueueScheduler alloc] initWithName:name targetQueue:dispatch_get_global_queue(priority, 0)];}
复制代码


RACScheduler 接口中另一个获得主线程调度器的方法 +mainThreadScheduler,其实现也是返回一个 RACTargetQueueScheduler 对象:


Objective-C


+ (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;}
复制代码


与前者不同的是,后者通过单例模式每次调用时返回一个相同的主线程队列。


抽象类 RACQueueScheduler

在我们对 RACTargetQueueScheduler 有一定了解之后,再看它的抽象类就非常简单了;RACImmediateScheduler 会立即执行传入的任务,而 RACQueueScheduler 其实就是对 GCD 的封装,相信各位读者从它的子类的实现就可以看出来。


RACQueueScheduler 对三个需要覆写的方法都进行了重写,其实现完全基于 GCD,以 -schedule: 方法为例:


Objective-C


- (RACDisposable *)schedule:(void (^)(void))block {  RACDisposable *disposable = [[RACDisposable alloc] init];
dispatch_async(self.queue, ^{ if (disposable.disposed) return; [self performAsCurrentScheduler:block]; });
return disposable;}
复制代码


使用 dispatch_async 方法直接将需要执行的任务异步派发到它所持有的队列上;而 -after:schedule: 方法的实现相信各位读者也能猜到:


Objective-C


- (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block {  RACDisposable *disposable = [[RACDisposable alloc] init];
dispatch_after([self.class wallTimeWithDate:date], self.queue, ^{ if (disposable.disposed) return; [self performAsCurrentScheduler:block]; });
return disposable;}
复制代码


哪怕不使用 RACScheduler,我们也能够想到利用 dispatch_after 完成一些需要延迟执行的任务,最后的 +after:repeatingEvery:withLeeway:schedule: 方法的实现就稍微复杂一些了:


Objective-C


- (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block {  uint64_t intervalInNanoSecs = (uint64_t)(interval * NSEC_PER_SEC);  uint64_t leewayInNanoSecs = (uint64_t)(leeway * NSEC_PER_SEC);
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.queue); dispatch_source_set_timer(timer, [self.class wallTimeWithDate:date], intervalInNanoSecs, leewayInNanoSecs); dispatch_source_set_event_handler(timer, block); dispatch_resume(timer);
return [RACDisposable disposableWithBlock:^{ dispatch_source_cancel(timer); }];}
复制代码


方法使用 dispatch_source_t 以及定时器,完成了每隔一段时间需要执行任务的需求。


RACSubscriptionScheduler

最后的 RACSubscriptionScheduler 是 ReactiveCocoa 中一个比较特殊的调度器,所有 ReactiveCocoa 中的订阅事件都会在 RACSubscriptionScheduler 调度器上进行;而它是通过封装两个调度器实现的:



backgroundScheduler 是一个优先级为 RACSchedulerPriorityDefault 的串行队列。


RACSubscriptionScheduler 本身不提供任何的调度功能,它会根据当前状态选择持有的两个调度器中的一个执行任务;首先判断当前线程是否存在 currentScheduler,如果不存在的话才会在 backgroundScheduler 执行任务。


Objective-C


- (RACDisposable *)schedule:(void (^)(void))block {  if (RACScheduler.currentScheduler == nil) return [self.backgroundScheduler schedule:block];  block();  return nil;}
- (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block { RACScheduler *scheduler = RACScheduler.currentScheduler ?: self.backgroundScheduler; return [scheduler after:date schedule:block];}
- (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block { RACScheduler *scheduler = RACScheduler.currentScheduler ?: self.backgroundScheduler; return [scheduler after:date repeatingEvery:interval withLeeway:leeway schedule:block];}
复制代码


RACSubscriptionScheduler 作为一个私有类,我们并不能直接在 ReactiveCocoa 外部使用它,需要通过私有方法 +subscriptionScheduler 获取这个调度器:


Objective-C


+ (RACScheduler *)subscriptionScheduler {  static dispatch_once_t onceToken;  static RACScheduler *subscriptionScheduler;  dispatch_once(&onceToken, ^{    subscriptionScheduler = [[RACSubscriptionScheduler alloc] init];  });
return subscriptionScheduler;}
复制代码


总结

RACScheduler 在某些方面与 GCD 中的队列十分相似,与 GCD 中的队列不同的有两点,第一,它可以通过 RACDisposable 对执行中的任务进行取消,第二是 RACScheduler 中任务的执行都是线性的;与此同时 RACScheduler 也与 NSOperationQueue 非常类似,但是它并不支持对调度的任务进行重排序以及实现任务与任务之间的依赖关系。


References

Github Repo:iOS-Source-Code-Analyze


Source: https://draveness.me/racscheduler


本文转载 Draveness 技术博客。


原文链接:https://draveness.me/racscheduler


2019 年 12 月 06 日 11:09158

评论

发布
暂无评论
发现更多内容

oeasy 教您玩转 linux 010214 画面转文字 asciiview

o

架构师期末作业

傻傻的帅

阿里P8大牛手写的源码笔记:Java集合+Java多线程+MyBatis+Spring

Java成神之路

Java spring 面试 多线程 mybatis源码

阿里P9技术专家:Java程序员这些必备技能的进阶书籍一定要读一读

Java成神之路

Java 学习 程序员 面试

高并发系列——CAS操作及CPU底层操作解析

诸葛小猿

CAS AtomicInteger compareAndSwap cmpxchg lock

有关 HashMap 面试会问的一切

小齐本齐

Java 数据结构 算法

云图说 | 华为云GPU共享型AI容器,让你用得起,用得好,用的放心

华为云开发者社区

gpu caffe

音乐创作者必备软件,轻松玩转原创

奈奈的杂社

音乐制作 编曲 电音 作曲 乐团

区块链交易系统开发,期货合约平台搭建

13823153121

区块链技术最重要价值所在

CECBC区块链专委会

区块链 数字经济 互联网革命

Spring IoC 到底是什么?

小齐本齐

spring 程序员 ioc Spring Framework Spring Bean

如何搭建第一个 Spring 项目?

小齐本齐

spring Spring Framework Spring Bean

Java String 面面观

keaper

Java string pool string

区块链技术与我们的生活将并存

CECBC区块链专委会

区块链 数字经济

开源决策树工具xDecision简介

赫杰辉

决策树 可视化 简化代码

SpringBoot写后端接口,看这一篇就够了!

华为云开发者社区

后端 swagger pringboot

Mysql学习笔记:InnoDB索引结构浅析

马迪奥

MySQL 索引结构 innodb

解Bug之路-记一次JVM堆外内存泄露Bug的查找

无毁的湖光

Linux JVM heap memory GC Linux Kenel

所见即所得的用户增长技术背后是如何实现的

海豚调度

用户增长 大数据技术 大数据架构 用户增长技术 ad-hoc技术

两年Java工作经验涨到23K,这究竟是怎么做到的?

Java架构师迁哥

Mysql学习笔记:分库分表(sharding)

马迪奥

MySQL Sharding

设计模式只是一把锤子

博文视点Broadview

读书笔记 编程 面向对象 设计模式

拥抱K8S系列-07-部署K8S集群(Rancher)

张无忌

Kubernetes rancher

我敢说,这个版本的斗地主你肯定没玩过?

华为云开发者社区

命令行 游戏 斗地主

Js 封装:阻止频繁重复操作

lockdown56

数字货币合约跟单系统开发app,跟单系统搭建源码

WX13823153201

不懂 ZooKeeper?没关系,这一篇给你讲的明明白白

海星

端-边-云全面协同创新 英特尔携手百度共推产业智能化升级

intel001

python——深入类和对象

菜鸟小sailor 🐕

一次代码评审,差点过不了试用期!

小傅哥

Java 小傅哥 代码质量 代码优化 代码规范

知识点总结

Acker飏

DNSPod与开源应用专场

DNSPod与开源应用专场

理解 RACScheduler 的实现-InfoQ