理解 RACScheduler 的實現

mx234 7年前發布 | 8K 次閱讀 iOS開發 移動開發 ReactiveCocoa

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/

 

 本文由用戶 mx234 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!