理解 RACScheduler 的實現
RACScheduler 是一個線性執行隊列,ReactiveCocoa 中的信號可以在 RACScheduler 上執行任務、發送結果;它的實現并不復雜,由多個簡單的方法和類組成整個 RACScheduler 模塊,是整個 ReactiveCocoa 中非常易于理解的部分。
RACScheduler 簡介
RACScheduler 作為 ReactiveCocoa 中唯一的用于調度的模塊,它包含很多個性化的子類:
RACScheduler 類的內部只有一個用于追蹤標記和 debug 的屬性 name ,頭文件和實現文件中的其它內容都是各種各樣的方法;我們可以把其中的方法分為兩類,一類是用于初始化 RACScheduler 實例的初始化方法:
另一類就是用于調度、執行任務的 +schedule: 等方法:
在圖中都省略了一些參數缺省的方法,以及一些調用其他方法的調度方法或者初始化方法,用以減少我們分析和理解整個 RACScheduler 類的難度。
在 RACScheduler 中,大部分的調度方法都是需要子類覆寫,它本身只提供少數的功能,比如遞歸 block 的執行:
- (RACDisposable *)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock {
RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
[self scheduleRecursiveBlock:[recursiveBlock copy] addingToDisposable:disposable];
return disposable;
}
該方法會遞歸的執行傳入的 recursiveBlock ,使用的方式非常簡單:
[scheduler scheduleRecursiveBlock:^(void (^reschedule)(void)) {
if (needTerminated) return;
// do something
reschedule();
}];
如果需要遞歸就執行方法中的 reschedule() ,就會再次執行當前的 block; -scheduleRecursiveBlock: 中調用的 -scheduleRecursiveBlock:addingToDisposable: 實現比較復雜:
- (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: 方法都需要子類覆寫:
- (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 返回一個它的實例:
+ (RACScheduler *)immediateScheduler {
static dispatch_once_t onceToken;
static RACScheduler *immediateScheduler;
dispatch_once(&onceToken, ^{
immediateScheduler = [[RACImmediateScheduler alloc] init];
});
return immediateScheduler;
}
由于 RACImmediateScheduler 是一個私有類,全局只能通過該方法返回它的實例,所以整個程序的運行周期內,我們通過『合法』手段只能獲得唯一一個單例。
作為 RACScheduler 的子類,它必須對父類的調度方法進行覆寫,不過因為本身的職能原因, RACImmediateScheduler 對于父類的覆寫還是非常簡單的:
- (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 的方法進行覆寫,不過它依然做了這件事情:
- (RACDisposable *)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock {
for (__block NSUInteger remaining = 1; remaining > 0; remaining--) {
recursiveBlock(^{
remaining++;
});
}
return nil;
}
實現的過程非常簡潔,甚至沒有什么值得解釋的地方了。
RACTargetQueueScheduler
RACTargetQueueScheduler 繼承自 RACQueueScheduler ,但是由于后者是抽象類,我們并不會直接使用它,它只是為前者提供必要的方法支持,將一部分邏輯抽離出來:
這里我們先簡單看一下 RACTargetQueueScheduler 的實現,整個 RACTargetQueueScheduler 類中只有一個初始化方法:
- (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 獲取全局的并行隊列,最終返回一個新的實例。
+ (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority name:(NSString *)name {
return [[RACTargetQueueScheduler alloc] initWithName:name targetQueue:dispatch_get_global_queue(priority, 0)];
}
在 RACScheduler 接口中另一個獲得主線程調度器的方法 +mainThreadScheduler ,其實現也是返回一個 RACTargetQueueScheduler 對象:
+ (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: 方法為例:
- (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: 方法的實現相信各位讀者也能猜到:
- (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: 方法的實現就稍微復雜一些了:
- (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 執行任務。
- (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 獲取這個調度器:
+ (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
Follow: Draveness · GitHub
Source: http://draveness.me/racscheduler
來自:http://draveness.me/racscheduler/