ReactiveCocoa 中 RACScheduler是如何封裝GCD的

前言

在使用ReactiveCocoa 過(guò)程中豌研,Josh AbernathyJustin Spahr-Summers 兩位大神為了能讓RAC的使用者更暢快的在沉浸在FRP的世界里对粪,更好的進(jìn)行并發(fā)編程缰犁,于是就對(duì)GCD進(jìn)行了一次封裝,并與RAC的各大組件進(jìn)行了完美的整合捡偏。

自從有了RACScheduler以后唤冈,使整個(gè)RAC并發(fā)編程的代碼里面更加和諧統(tǒng)一,更加順手银伟,更加“ReactiveCocoa”你虹。

目錄

  • 1.RACScheduler是如何封裝GCD的
  • 2.RACScheduler的一些子類
  • 3.RACScheduler是如何“取消”并發(fā)任務(wù)的
  • 4.RACScheduler是如何和RAC其他組件進(jìn)行完美整合的

一. RACScheduler是如何封裝GCD的

RACScheduler在ReactiveCocoa中到底是干嘛的呢?處于什么地位呢彤避?官方給出的定義如下:


Schedulers are used to control when and where work is performed

RACScheduler在ReactiveCocoa中是用來(lái)控制一個(gè)任務(wù)傅物,何時(shí)何地被執(zhí)行。它主要是用來(lái)解決ReactiveCocoa中并發(fā)編程的問(wèn)題的琉预。

RACScheduler的實(shí)質(zhì)是對(duì)GCD的封裝董饰,底層就是GCD實(shí)現(xiàn)的。

要分析RACScheduler圆米,先來(lái)回顧一下GCD卒暂。

眾所周知,在GCD中娄帖,Dispatch Queue主要分為2類也祠,Serial Dispatch Queue 和 Concurrent Dispatch Queue 。其中Serial Dispatch Queue是等待現(xiàn)在執(zhí)行中處理結(jié)束的隊(duì)列块茁,Concurrent Dispatch Queue是不等待現(xiàn)在執(zhí)行中處理結(jié)束的隊(duì)列齿坷。

生成Dispatch Queue的方法也有2種,第一種方式是通過(guò)GCD的API生成Dispatch Queue数焊。

生成Serial Dispatch Queue


dispatch_queue_t serialDispatchQueue = dispatch_queue_create("com.gcd.SerialDispatchQueue", DISPATCH_QUEUE_SERIAL);
    


生成Concurrent Dispatch Queue



dispatch_queue_t concurrentDispatchQueue = dispatch_queue_create("com.gcd.ConcurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);

第二種方法是直接獲取系統(tǒng)提供的Dispatch Queue永淌。系統(tǒng)提供的也分為2類,Main Dispatch Queue 和 Global Dispatch Queue佩耳。Main Dispatch Queue 對(duì)應(yīng)著是Serial Dispatch Queue遂蛀,Global Dispatch Queue 對(duì)應(yīng)著是Concurrent Dispatch Queue。

Global Dispatch Queue主要分為8種干厚。

首先是以下4種李滴,分別是優(yōu)先級(jí)對(duì)應(yīng)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個(gè)優(yōu)先級(jí),所以一共8種Global Dispatch Queue挂捅。帶有 overcommit 的隊(duì)列表示每當(dāng)有任務(wù)提交時(shí)芹助,系統(tǒng)都會(huì)新開(kāi)一個(gè)線程處理,這樣就不會(huì)造成某個(gè)線程過(guò)載(overcommit)闲先。

回到RACScheduler中來(lái)状土,RACScheduler既然是對(duì)GCD的封裝,那么上述說(shuō)的這些類型也都有其一一對(duì)應(yīng)的封裝伺糠。


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中的優(yōu)先級(jí)蒙谓,這里只封裝了4種,也是分別對(duì)應(yīng)GCD中的DISPATCH_QUEUE_PRIORITY_HIGH训桶,DISPATCH_QUEUE_PRIORITY_DEFAULT累驮,DISPATCH_QUEUE_PRIORITY_LOW,DISPATCH_QUEUE_PRIORITY_BACKGROUND渊迁。

RACScheduler有6個(gè)類方法慰照,都是用來(lái)生成一個(gè)queue的。


+ (RACScheduler *)immediateScheduler;
+ (RACScheduler *)mainThreadScheduler;

+ (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority name:(NSString *)name;
+ (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority;
+ (RACScheduler *)scheduler;

+ (RACScheduler *)currentScheduler;



接下來(lái)依次分析一下它們的底層實(shí)現(xiàn)琉朽。

1. immediateScheduler


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


immediateScheduler底層實(shí)現(xiàn)就是生成了一個(gè)RACImmediateScheduler的單例毒租。

RACImmediateScheduler 是繼承自RACScheduler。


@interface RACImmediateScheduler : RACScheduler
@end

在RACScheduler中箱叁,每個(gè)種類的RACScheduler都會(huì)有一個(gè)name屬性墅垮,名字也算是他們的標(biāo)示。RACImmediateScheduler的name是@"com.ReactiveCocoa.RACScheduler.immediateScheduler"

RACImmediateScheduler的作用和它的名字一樣耕漱,是立即執(zhí)行閉包里面的任務(wù)算色。


- (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:方法中,直接調(diào)用執(zhí)行入?yún)lock( )閉包螟够。在after: schedule:方法中灾梦,線程先睡眠峡钓,直到date的時(shí)刻,再醒過(guò)來(lái)執(zhí)行入?yún)lock( )閉包若河。


- (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;
}


當(dāng)然RACImmediateScheduler是不可能支持after: repeatingEvery: withLeeway: schedule:方法的能岩。因?yàn)樗亩x就是立即執(zhí)行的,不應(yīng)該repeat萧福。


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

RACImmediateScheduler的scheduleRecursiveBlock:方法中只要recursiveBlock閉包存在拉鹃,就會(huì)無(wú)限遞歸調(diào)用執(zhí)行,除非recursiveBlock不存在了鲫忍。

2. mainThreadScheduler

mainThreadScheduler也是一個(gè)類型是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中悟民,只有一個(gè)初始化方法坝辫。


- (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];
}

先新建了一個(gè)queue,name是@"com.ReactiveCocoa.RACScheduler.mainThreadScheduler"射亏,類型是Serial Dispatch Queue 類型的阀溶,然后調(diào)用了dispatch_set_target_queue方法。

所以重點(diǎn)就在dispatch_set_target_queue方法里面了鸦泳。

dispatch_set_target_queue方法主要有兩個(gè)目的:一是設(shè)置dispatch_queue_create創(chuàng)建隊(duì)列的優(yōu)先級(jí)银锻,二是建立隊(duì)列的執(zhí)行階層。

  • 當(dāng)使用dispatch_queue_create創(chuàng)建隊(duì)列的時(shí)候做鹰,不管是串行還是并行击纬,它們的優(yōu)先級(jí)都是DISPATCH_QUEUE_PRIORITY_DEFAULT級(jí)別,而這個(gè)API就是可以設(shè)置隊(duì)列的優(yōu)先級(jí)钾麸。

舉個(gè)例子:


dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
//注意:被設(shè)置優(yōu)先級(jí)的隊(duì)列是第一個(gè)參數(shù)更振。
dispatch_set_target_queue(serialQueue, globalQueue);

通過(guò)上面的代碼,就把將serailQueue設(shè)置成DISPATCH_QUEUE_PRIORITY_HIGH饭尝。

  • 使用這個(gè)dispatch_set_target_queue方法可以設(shè)置隊(duì)列執(zhí)行階層肯腕,例如dispatch_set_target_queue(queue, targetQueue);
    這樣設(shè)置時(shí),相當(dāng)于將queue指派給targetQueue钥平,如果targetQueue是串行隊(duì)列实撒,則queue是串行執(zhí)行的;如果targetQueue是并行隊(duì)列涉瘾,那么queue是并行的知态。

舉個(gè)例子:


    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,那么輸出結(jié)果必定如下:


queue1 1
queue1 2
queue2 1
queue2 2
target queue


如果targetQueue為Concurrent Dispatch Queue立叛,那么輸出結(jié)果可能如下:



queue1 1
queue2 1
queue1 2
target queue
queue2 2


回到RACTargetQueueScheduler中來(lái)负敏,在這里傳進(jìn)來(lái)的入?yún)⑹莇ispatch_get_main_queue( ),這是一個(gè)Serial Dispatch Queue秘蛇,這里再調(diào)用dispatch_set_target_queue方法其做,相當(dāng)于把queue的優(yōu)先級(jí)設(shè)置的和main_queue一致顶考。

3. scheduler

以下三個(gè)方法實(shí)質(zhì)是同一個(gè)方法。



+ (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority name:(NSString *)name;
+ (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority;
+ (RACScheduler *)scheduler;



+ (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];
}


通過(guò)源碼我們能知道妖泄,scheduler這一系列的三個(gè)方法村怪,是創(chuàng)建了一個(gè) Global Dispatch Queue,對(duì)應(yīng)的屬于Concurrent Dispatch Queue浮庐。

schedulerWithPriority: name:方法可以指定線程的優(yōu)先級(jí)和名字。

schedulerWithPriority:方法只能執(zhí)行優(yōu)先級(jí)柬焕,名字為默認(rèn)的@"com.ReactiveCocoa.RACScheduler.backgroundScheduler"审残。

scheduler方法創(chuàng)建出來(lái)的queue的優(yōu)先級(jí)是默認(rèn)的,名字也是默認(rèn)的@"com.ReactiveCocoa.RACScheduler.backgroundScheduler"斑举。

注意搅轿,scheduler和mainThreadScheduler,immediateScheduler這兩個(gè)單例不同的是富玷,scheduler每次都會(huì)創(chuàng)建一個(gè)新的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 中定義了這么一個(gè)key赎懦,@"RACSchedulerCurrentSchedulerKey"雀鹃,這個(gè)用來(lái)從線程字典里面存取出對(duì)應(yīng)的RACScheduler。


NSString * const RACSchedulerCurrentSchedulerKey = @"RACSchedulerCurrentSchedulerKey";

在currentScheduler這個(gè)方法里面看到的是從線程字典里面取出一個(gè)RACScheduler励两。至于什么時(shí)候存的黎茎,下面會(huì)解釋到。

如果能從線程字典里面取出一個(gè)RACScheduler当悔,就返回取出的RACScheduler傅瞻。如果字典里面沒(méi)有,再判斷當(dāng)前的scheduler是否是在主線程上盲憎。


+ (BOOL)isOnMainThread {
    return [NSOperationQueue.currentQueue isEqual:NSOperationQueue.mainQueue] || [NSThread isMainThread];
}


判斷方法如上嗅骄,只要是NSOperationQueue在mainQueue上,或者NSThread是主線程饼疙,都算是在主線程上溺森。

如果是在主線程上,就返回mainThreadScheduler窑眯。
如果既不在主線程上儿惫,線程字典里面也找不到對(duì)應(yīng)key值對(duì)應(yīng)的value,那么就返回nil伸但。

RACScheduler除了有6個(gè)類方法肾请,還有4個(gè)實(shí)例方法:


- (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個(gè)方法其實(shí)從名字上就知道是用來(lái)干嘛的。

schedule:是為RACScheduler添加一個(gè)任務(wù)更胖,入?yún)⑹且粋€(gè)閉包铛铁。

after: schedule:是為RACScheduler添加一個(gè)定時(shí)任務(wù)隔显,在date時(shí)間之后才執(zhí)行任務(wù)。

afterDelay: schedule:是為RACScheduler添加一個(gè)延時(shí)執(zhí)行的任務(wù)饵逐,延時(shí)delay時(shí)間之后才執(zhí)行任務(wù)括眠。

after: repeatingEvery: withLeeway: schedule:是為RACScheduler添加一個(gè)定時(shí)任務(wù),在date時(shí)間之后才開(kāi)始執(zhí)行倍权,然后每隔interval秒執(zhí)行一次任務(wù)掷豺。

這四個(gè)方法會(huì)分別在RACScheduler的各個(gè)子類里面進(jìn)行重寫。

比如之前提到的immediateScheduler薄声,schedule:方法中會(huì)直接立即執(zhí)行閉包当船。after: schedule:方法中添加一個(gè)定時(shí)任務(wù),在date時(shí)間之后才執(zhí)行任務(wù)默辨。after: repeatingEvery: withLeeway: schedule:這個(gè)方法在RACImmediateScheduler中就直接返回nil德频。

還有其他子類在下面會(huì)分析這4個(gè)方法的實(shí)現(xiàn)。

另外還有最后3個(gè)方法


- (RACDisposable *)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock;
- (void)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock addingToDisposable:(RACCompoundDisposable *)disposable
- (void)performAsCurrentScheduler:(void (^)(void))block;


前兩個(gè)方法是實(shí)現(xiàn)RACSequence中signalWithScheduler:方法的缩幸,具體分析見(jiàn)這篇文章

performAsCurrentScheduler:方法是在RACQueueScheduler中使用到了壹置,在下面子類分析里面詳細(xì)分析。

二. RACScheduler的一些子類

RACScheduler總共有以下5個(gè)子類表谊。

1. RACTestScheduler

這個(gè)類主要是一個(gè)測(cè)試類钞护,主要用在單元測(cè)試中,它是用來(lái)驗(yàn)證異步調(diào)用沒(méi)有花費(fèi)大量的時(shí)間等待爆办。RACTestScheduler也可以用在多線程當(dāng)中患亿,當(dāng)時(shí)一次只能在排隊(duì)的方法隊(duì)列中選擇一個(gè)方法執(zhí)行。


@interface RACTestSchedulerAction : NSObject
@property (nonatomic, copy, readonly) NSDate *date;
@property (nonatomic, copy, readonly) void (^block)(void);
@property (nonatomic, strong, readonly) RACDisposable *disposable;

- (id)initWithDate:(NSDate *)date block:(void (^)(void))block;
@end

在單元測(cè)試中押逼,ReactiveCocoa為了方便比較每個(gè)方法的調(diào)用步藕,新建了一個(gè)RACTestSchedulerAction對(duì)象,用來(lái)更加方便的比較和描述測(cè)試的全過(guò)程挑格。RACTestSchedulerAction的定義如上×撸現(xiàn)在再來(lái)解釋一下參數(shù)。

date是一個(gè)時(shí)間漂彤,時(shí)間主要是用來(lái)比較和決定下一次該輪到哪個(gè)閉包要開(kāi)始執(zhí)行了雾消。

void (^block)(void)閉包是RACScheduler中的一個(gè)任務(wù)。

disposable是控制一個(gè)action是否可以執(zhí)行的挫望。一旦disposed了立润,那么這個(gè)action就不會(huì)被執(zhí)行。

initWithDate: block: 方法是初始化一個(gè)新的action媳板。

在單元測(cè)試過(guò)程中桑腮,需要調(diào)用step方法來(lái)進(jìn)行查看每次調(diào)用閉包的情況。


- (void)step {
    [self step:1];
}

- (void)stepAll {
    [self step:NSUIntegerMax];
}

step和stepAll方法都是調(diào)用step:方法蛉幸。step只是執(zhí)行一次RACScheduler中的任務(wù)破讨,stepAll是執(zhí)行所有的RACScheduler中的任務(wù)丛晦。既然都是調(diào)用step:,那接下來(lái)分析一下step:的具體實(shí)現(xiàn)提陶。


- (void)step:(NSUInteger)ticks {
    @synchronized (self) {
        for (NSUInteger i = 0; i < ticks; i++) {
            const void *actionPtr = NULL;
            if (!CFBinaryHeapGetMinimumIfPresent(self.scheduledActions, &actionPtr)) break;
            
            RACTestSchedulerAction *action = (__bridge id)actionPtr;
            CFBinaryHeapRemoveMinimumValue(self.scheduledActions);
            
            if (action.disposable.disposed) continue;
            
            RACScheduler *previousScheduler = RACScheduler.currentScheduler;
            NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = self;
            
            action.block();
            
            if (previousScheduler != nil) {
                NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = previousScheduler;
            } else {
                [NSThread.currentThread.threadDictionary removeObjectForKey:RACSchedulerCurrentSchedulerKey];
            }
        }
    }
}


step:的實(shí)現(xiàn)主要就是一個(gè)for循環(huán)烫沙。循環(huán)的次數(shù)就是入?yún)icks決定的。首先const void *actionPtr是一個(gè)指向函數(shù)的指針隙笆。在上述實(shí)現(xiàn)中有一個(gè)很重要的函數(shù)——CFBinaryHeapGetMinimumIfPresent锌蓄。該函數(shù)的原型如下:


Boolean CFBinaryHeapGetMinimumIfPresent(CFBinaryHeapRef heap, const void **value)

這個(gè)函數(shù)的主要作用的是在二分堆heap中查找一個(gè)最小值。


static CFComparisonResult RACCompareScheduledActions(const void *ptr1, const void *ptr2, void *info) {
    RACTestSchedulerAction *action1 = (__bridge id)ptr1;
    RACTestSchedulerAction *action2 = (__bridge id)ptr2;
    return CFDateCompare((__bridge CFDateRef)action1.date, (__bridge CFDateRef)action2.date, NULL);
}

比較規(guī)則如上撑柔,就是比較兩者的date的值瘸爽。從二分堆中找出這樣一個(gè)最小值,對(duì)應(yīng)的就是scheduler中的任務(wù)乏冀。如果最小值有幾個(gè)相等最小值,就隨機(jī)返回一個(gè)最小值洋只。返回的函數(shù)放在actionPtr中辆沦。整個(gè)函數(shù)的返回值是一個(gè)BOOL值,如果二分堆不為空识虚,能找到最小值就返回YES肢扯,如果二分堆為空,就找不到最小值了担锤,就返回NO蔚晨。

stepAll方法里面?zhèn)魅肓薔SUIntegerMax,這個(gè)for循環(huán)也不會(huì)死循環(huán)肛循,因?yàn)榈蕉阎兴械娜蝿?wù)都執(zhí)行完成之后铭腕,CFBinaryHeapGetMinimumIfPresent返回NO,就會(huì)執(zhí)行break多糠,跳出循環(huán)累舷。

這里會(huì)把currentScheduler保存到線程字典里面。接著會(huì)執(zhí)行action.block夹孔,執(zhí)行任務(wù)被盈。


- (RACDisposable *)schedule:(void (^)(void))block {
    NSCParameterAssert(block != nil);
    
    @synchronized (self) {
        NSDate *uniqueDate = [NSDate dateWithTimeIntervalSinceReferenceDate:self.numberOfDirectlyScheduledBlocks];
        self.numberOfDirectlyScheduledBlocks++;
        
        RACTestSchedulerAction *action = [[RACTestSchedulerAction alloc] initWithDate:uniqueDate block:block];
        CFBinaryHeapAddValue(self.scheduledActions, (__bridge void *)action);
        
        return action.disposable;
    }
}

- (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block {
    NSCParameterAssert(date != nil);
    NSCParameterAssert(block != nil);
    
    @synchronized (self) {
        RACTestSchedulerAction *action = [[RACTestSchedulerAction alloc] initWithDate:date block:block];
        CFBinaryHeapAddValue(self.scheduledActions, (__bridge void *)action);
        
        return action.disposable;
    }
}


schedule:方法里面會(huì)累加numberOfDirectlyScheduledBlocks值,這個(gè)值也會(huì)初始化成時(shí)間搭伤,以便比較各個(gè)方法該調(diào)度的時(shí)間只怎。numberOfDirectlyScheduledBlocks最終會(huì)代表總共有多少個(gè)block任務(wù)產(chǎn)生了。然后用CFBinaryHeapAddValue加入到堆中怜俐。

after:schedule:就是直接新建RACTestSchedulerAction對(duì)象身堡,然后再用CFBinaryHeapAddValue把block閉包加入到堆中。

after: repeatingEvery: withLeeway: schedule:同樣也是新建RACTestSchedulerAction對(duì)象拍鲤,然后再用CFBinaryHeapAddValue把block閉包加入到堆中盾沫。

2. RACSubscriptionScheduler

RACSubscriptionScheduler是RACScheduler最后一個(gè)單例裁赠。RACScheduler中唯一的三個(gè)單例現(xiàn)在就齊全了:RACImmediateScheduler,RACTargetQueueScheduler 赴精,RACSubscriptionScheduler佩捞。



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

RACSubscriptionScheduler 的名字是@"com.ReactiveCocoa.RACScheduler.subscriptionScheduler"


- (id)init {
    self = [super initWithName:@"com.ReactiveCocoa.RACScheduler.subscriptionScheduler"];
    if (self == nil) return nil;
    _backgroundScheduler = [RACScheduler scheduler];  
    return self;
}

RACSubscriptionScheduler初始化的時(shí)候會(huì)新建一個(gè)Global Dispatch Queue。


- (RACDisposable *)schedule:(void (^)(void))block {
    NSCParameterAssert(block != NULL);
    if (RACScheduler.currentScheduler == nil) return [self.backgroundScheduler schedule:block];
    block();
    return nil;
}

如果RACScheduler.currentScheduler為nil就用backgroundScheduler去調(diào)用block閉包蕾哟,否則就執(zhí)行block閉包一忱。


- (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];
}



兩個(gè)after方法都有取出RACScheduler.currentScheduler,如果為空就用self.backgroundScheduler去調(diào)用各自的after的方法谭确。

RACSubscriptionScheduler中的backgroundScheduler的意義就在此帘营,當(dāng)RACScheduler.currentScheduler不存在的時(shí)候就會(huì)替換成self.backgroundScheduler。

3. RACImmediateScheduler

這個(gè)子類在分析immediateScheduler方法的時(shí)候逐哈,詳細(xì)分析過(guò)了芬迄,這里不再贅述。

4. RACQueueScheduler

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

schedule:會(huì)調(diào)用performAsCurrentScheduler:方法昂秃。


- (void)performAsCurrentScheduler:(void (^)(void))block {
    NSCParameterAssert(block != NULL);
    
    RACScheduler *previousScheduler = RACScheduler.currentScheduler;
    NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = self;
    
    @autoreleasepool {
        block();
    }
    
    if (previousScheduler != nil) {
        NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = previousScheduler;
    } else {
        [NSThread.currentThread.threadDictionary removeObjectForKey:RACSchedulerCurrentSchedulerKey];
    }
}



performAsCurrentScheduler:方法會(huì)先在調(diào)用block( )之前禀梳,把當(dāng)前的scheduler存入線程字典中。

試想肠骆,如果現(xiàn)在在一個(gè)Concurrent Dispatch Queue中算途,在執(zhí)行block( )之前需要先切換線程,切換到當(dāng)前scheduler中蚀腿。當(dāng)執(zhí)行完block閉包之后嘴瓤,previousScheduler如果不為nil,那么就還原現(xiàn)場(chǎng)莉钙,線程字典里面再存回原來(lái)的scheduler廓脆,反之previousScheduler為nil,那么就移除掉線程字典里面的key磁玉。

這里需要值得注意的是:

scheduler本質(zhì)其實(shí)是一個(gè)quene狞贱,并不是一個(gè)線程。它只能保證里面的線程都是串行執(zhí)行的蜀涨,但是它不能保證每個(gè)線程不一定都是在同一個(gè)線程里面執(zhí)行瞎嬉。

如上面這段performAsCurrentScheduler:的實(shí)現(xiàn)所表現(xiàn)的那樣。所以
在scheduler使用Core Data很容易崩潰厚柳,很可能跑到子線程上面去了氧枣。一旦寫數(shù)據(jù)的時(shí)候到了子線程上,很容易就Crash了别垮。一定要記得回到main queue上便监。


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

在after中調(diào)用dispatch_after方法,經(jīng)過(guò)date時(shí)間之后再調(diào)用performAsCurrentScheduler:。

wallTimeWithDate:的實(shí)現(xiàn)如下:


+ (dispatch_time_t)wallTimeWithDate:(NSDate *)date {
    NSCParameterAssert(date != nil);
    
    double seconds = 0;
    double frac = modf(date.timeIntervalSince1970, &seconds);
    
    struct timespec walltime = {
        .tv_sec = (time_t)fmin(fmax(seconds, LONG_MIN), LONG_MAX),
        .tv_nsec = (long)fmin(fmax(frac * NSEC_PER_SEC, LONG_MIN), LONG_MAX)
    };
    
    return dispatch_walltime(&walltime, 0);
}

dispatch_walltime函數(shù)是由POSIX中使用的struct timespec類型的時(shí)間得到dispatch_time_t類型的值烧董。dispatch_time函數(shù)通常用于計(jì)算相對(duì)時(shí)間毁靶,而dispatch_walltime函數(shù)用于計(jì)算絕對(duì)時(shí)間。

這段代碼其實(shí)很簡(jiǎn)單逊移,就是把date的時(shí)間轉(zhuǎn)換成一個(gè)dispatch_time_t類型的预吆。由NSDate類對(duì)象獲取能傳遞給dispatch_after函數(shù)的dispatch_time_t類型的值。


- (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block {
   NSCParameterAssert(date != nil);
   NSCParameterAssert(interval > 0.0 && interval < INT64_MAX / NSEC_PER_SEC);
   NSCParameterAssert(leeway >= 0.0 && leeway < INT64_MAX / NSEC_PER_SEC);
   NSCParameterAssert(block != NULL);
   
   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);
   }];
}



after: repeatingEvery: withLeeway: schedule:方法里面的實(shí)現(xiàn)就是用GCD在self.queue上創(chuàng)建了一個(gè)Timer胳泉,時(shí)間間隔是interval拐叉,修正時(shí)間是leeway。

leeway這個(gè)參數(shù)是為dispatch source指定一個(gè)期望的定時(shí)器事件精度扇商,讓系統(tǒng)能夠靈活地管理并喚醒內(nèi)核凤瘦。例如系統(tǒng)可以使用leeway值來(lái)提前或延遲觸發(fā)定時(shí)器,使其更好地與其它系統(tǒng)事件結(jié)合案铺。創(chuàng)建自己的定時(shí)器時(shí)蔬芥,應(yīng)該盡量指定一個(gè)leeway值。不過(guò)就算指定leeway值為0控汉,也不能完完全全期望定時(shí)器能夠按照精確的納秒來(lái)觸發(fā)事件笔诵。

這個(gè)定時(shí)器在interval執(zhí)行入?yún)㈤]包。在取消任務(wù)的時(shí)候調(diào)用dispatch_source_cancel取消定時(shí)器timer暇番。

5. RACTargetQueueScheduler

這個(gè)子類在分析mainThreadScheduler方法的時(shí)候嗤放,詳細(xì)分析過(guò)了思喊,這里不再贅述壁酬。

三. RACScheduler是如何“取消”并發(fā)任務(wù)的

既然RACScheduler是對(duì)GCD的封裝,那么在GCD的上層可以實(shí)現(xiàn)一些GCD所無(wú)法完成的“特性”恨课。這里的“特性”是打引號(hào)的舆乔,因?yàn)榈讓邮荊CD,上層的特性只能通過(guò)一些特殊手段來(lái)實(shí)現(xiàn)看似是新的特性剂公。在這一點(diǎn)上希俩,RACScheduler就實(shí)現(xiàn)了GCD沒(méi)有的特性——“取消”任務(wù)。

Operation Queues :
相對(duì) GCD來(lái)說(shuō)纲辽,使用 Operation Queues 會(huì)增加一點(diǎn)點(diǎn)額外的開(kāi)銷颜武,但是卻換來(lái)了非常強(qiáng)大的靈活性和功能,它可以給 operation 之間添加依賴關(guān)系拖吼、取消一個(gè)正在執(zhí)行的 operation 鳞上、暫停和恢復(fù) operation queue 等;

GCD:
是一種更輕量級(jí)的吊档,以 FIFO的順序執(zhí)行并發(fā)任務(wù)的方式篙议,使用 GCD時(shí)我們可以并不關(guān)心任務(wù)的調(diào)度情況,而讓系統(tǒng)幫我們自動(dòng)處理。但是 GCD的缺陷也是非常明顯的鬼贱,想要給任務(wù)之間添加依賴關(guān)系移怯、取消或者暫停一個(gè)正在執(zhí)行的任務(wù)時(shí)就會(huì)變得非常棘手。

既然GCD不方便取消一個(gè)任務(wù)这难,那么RACScheduler是怎么做到的呢舟误?

這就體現(xiàn)在RACQueueScheduler上⊙慵眩回頭看看RACQueueScheduler的schedule:實(shí)現(xiàn) 和 after: schedule:實(shí)現(xiàn)脐帝。

最核心的代碼:


 dispatch_async(self.queue, ^{
      if (disposable.disposed) return;
      [self performAsCurrentScheduler:block];
 });

在調(diào)用performAsCurrentScheduler:之前,加了一個(gè)判斷糖权,判斷當(dāng)前是否取消了任務(wù)堵腹,如果取消了任務(wù),就return星澳,不會(huì)調(diào)用block閉包疚顷。這樣就實(shí)現(xiàn)了取消任務(wù)的“假象”。

四. RACScheduler是如何和RAC其他組件進(jìn)行完美整合的

在整個(gè)ReactiveCocoa中禁偎,利用RACScheduler實(shí)現(xiàn)了很多操作腿堤,和RAC是深度整合的。這里就來(lái)總結(jié)總結(jié)ReactiveCocoa中總共有哪些地方用到了RACScheduler如暖。

在ReactiveCocoa 中全局搜索RACScheduler笆檀,遍歷完所有庫(kù),RACScheduler就用在以下10個(gè)類中盒至。下面就來(lái)看看是如何用在這些地方的酗洒。

從下面這些地方使用了Scheduler中,我們就可以了解到哪些操作是在子線程枷遂,哪些是在主線程樱衷。區(qū)分出了這些,對(duì)于線程不安全的操作酒唉,我們就能心有成足的處理好它們矩桂,讓它們回到主線程中去操作,這樣就可以減少很多莫名的Crash痪伦。這些Crash都是因?yàn)榫€程問(wèn)題導(dǎo)致的侄榴。

1. 在RACCommand中

- (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock


這個(gè)方法十分復(fù)雜,里面用到了RACScheduler.immediateScheduler网沾,deliverOn:RACScheduler.mainThreadScheduler癞蚕。具體的源碼分析會(huì)在下一篇RACCommand源碼分析里面詳細(xì)分析。


- (RACSignal *)execute:(id)input

在這個(gè)方法中绅这,會(huì)調(diào)用subscribeOn:RACScheduler.mainThreadScheduler涣达。

2. 在RACDynamicSignal中


- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    NSCParameterAssert(subscriber != nil);
    
    RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
    subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];
    
    if (self.didSubscribe != NULL) {
        RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
            RACDisposable *innerDisposable = self.didSubscribe(subscriber);
            [disposable addDisposable:innerDisposable];
        }];
        
        [disposable addDisposable:schedulingDisposable];
    }
    
    return disposable;
}

在RACDynamicSignal的subscribe:訂閱過(guò)程中會(huì)用到subscriptionScheduler。于是對(duì)這個(gè)scheduler調(diào)用schedule:就會(huì)執(zhí)行下面這段代碼:


- (RACDisposable *)schedule:(void (^)(void))block {
    NSCParameterAssert(block != NULL);
    
    if (RACScheduler.currentScheduler == nil) return [self.backgroundScheduler schedule:block];
    
    block();
    return nil;
}

如果currentScheduler不為空,閉包會(huì)在currentScheduler中執(zhí)行度苔,如果currentScheduler為空匆篓,閉包就會(huì)在backgroundScheduler中執(zhí)行,這是一個(gè)Global Dispatch Queue寇窑,優(yōu)先級(jí)是RACSchedulerPriorityDefault鸦概。

同理,在RACEmptySignal甩骏,RACErrorSignal窗市,RACReturnSignal,RACSignal的相關(guān)的signal的訂閱中也都會(huì)調(diào)用subscriptionScheduler饮笛。

3. 在RACBehaviorSubject中

- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    RACDisposable *subscriptionDisposable = [super subscribe:subscriber];
    
    RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
        @synchronized (self) {
            [subscriber sendNext:self.currentValue];
        }
    }];
    
    return [RACDisposable disposableWithBlock:^{
        [subscriptionDisposable dispose];
        [schedulingDisposable dispose];
    }];
}


在RACBehaviorSubject的subscribe:訂閱過(guò)程中會(huì)用到subscriptionScheduler咨察。于是對(duì)這個(gè)scheduler調(diào)用schedule:,代碼在上面分析過(guò)了福青。

同理摄狱,如果currentScheduler不為空,閉包會(huì)在currentScheduler中執(zhí)行无午,如果currentScheduler為空媒役,閉包就會(huì)在backgroundScheduler中執(zhí)行,這是一個(gè)Global Dispatch Queue宪迟,優(yōu)先級(jí)是RACSchedulerPriorityDefault酣衷。

4. 在RACReplaySubject中

它的訂閱也同上面信號(hào)的訂閱一樣,會(huì)調(diào)用subscriptionScheduler次泽。

由于RACReplaySubject是在子線程上穿仪,所以建議在使用Core Data這些不安全庫(kù)的時(shí)候一定要記得加上deliverOn。

5. 在RACSequence中

在RACSequence中箕憾,以下兩個(gè)方法用到了RACScheduler:


- (RACSignal *)signal {
    return [[self signalWithScheduler:[RACScheduler scheduler]] setNameWithFormat:@"[%@] -signal", self.name];
}



- (RACSignal *)signalWithScheduler:(RACScheduler *)scheduler {
    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        __block RACSequence *sequence = self;
        
        return [scheduler scheduleRecursiveBlock:^(void (^reschedule)(void)) {
            if (sequence.head == nil) {
                [subscriber sendCompleted];
                return;
            }
            
            [subscriber sendNext:sequence.head];
            
            sequence = sequence.tail;
            reschedule();
        }];
    }] setNameWithFormat:@"[%@] -signalWithScheduler: %@", self.name, scheduler];
}


上面兩個(gè)方法會(huì)調(diào)用RACScheduler中的scheduleRecursiveBlock:方法牡借。關(guān)于這個(gè)方法的源碼分析可以看RACSequence的源碼分析拳昌。

6. 在RACSignal+Operations中

這里總共有9個(gè)方法用到了Scheduler袭异。

第一個(gè)方法:


static RACDisposable *subscribeForever (RACSignal *signal, void (^next)(id), void (^error)(NSError *, RACDisposable *), void (^completed)(RACDisposable *))

在上面這個(gè)方法里面用到了


RACScheduler *recursiveScheduler = RACScheduler.currentScheduler ?: [RACScheduler scheduler];

取出currentScheduler或者一個(gè)Global Dispatch Queue,然后調(diào)用scheduleRecursiveBlock:炬藤。

第二個(gè)方法:



- (RACSignal *)throttle:(NSTimeInterval)interval valuesPassingTest:(BOOL (^)(id next))predicate

在上面這個(gè)方法中會(huì)調(diào)用


RACScheduler *scheduler = [RACScheduler scheduler];
RACScheduler *delayScheduler = RACScheduler.currentScheduler ?: scheduler

在delayScheduler中調(diào)用afterDelay: schedule:方法御铃,這也是throttle:valuesPassingTest:方法實(shí)現(xiàn)的很重要的一步。

第三個(gè)方法:


- (RACSignal *)delay:(NSTimeInterval)interval

由于這是一個(gè)延遲方法沈矿,肯定是會(huì)調(diào)用Scheduler的after方法上真。


   RACScheduler *delayScheduler = RACScheduler.currentScheduler ?: scheduler;
   RACDisposable *schedulerDisposable = [delayScheduler afterDelay:interval schedule:block];

RACScheduler.currentScheduler ?: scheduler 這個(gè)判斷在上述幾個(gè)時(shí)間相關(guān)的方法都用到了。

所以羹膳,這里給一個(gè)建議:**
delay由于不一定會(huì)回到當(dāng)前線程中睡互,所以delay之后再去訂閱可能就在子線程中去執(zhí)行。所以使用delay的時(shí)候最好追加一個(gè)deliverOn。**

第四個(gè)方法:


- (RACSignal *)bufferWithTime:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler

在這個(gè)方法中理所當(dāng)然的需要調(diào)用[scheduler afterDelay:interval schedule:flushValues]這個(gè)方法就珠,來(lái)達(dá)到延遲的目的寇壳,從而實(shí)現(xiàn)緩沖buffer的效果。

第五個(gè)方法:


+ (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler

第六個(gè)方法:


+ (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler withLeeway:(NSTimeInterval)leeway { }

第五個(gè)方法 和 第六個(gè)方法都用傳進(jìn)去的入?yún)cheduler去調(diào)用after: repeatingEvery: withLeeway: schedule:方法妻怎。

第七個(gè)方法:


- (RACSignal *)timeout:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler { }

在這個(gè)方法中會(huì)用入?yún)cheduler調(diào)用afterDelay: schedule:壳炎,延遲一段時(shí)候后,執(zhí)行[disposable dispose]逼侦,從而也實(shí)現(xiàn)了超時(shí)發(fā)送sendError:匿辩。

第八個(gè)方法:


- (RACSignal *)deliverOn:(RACScheduler *)scheduler { }

第九個(gè)方法:


- (RACSignal *)subscribeOn:(RACScheduler *)scheduler { }

第八個(gè)方法 和 第九個(gè)方法都是根據(jù)入?yún)cheduler去調(diào)用schedule:方法。入?yún)⑹鞘裁搭愋偷膕cheduler決定了schedule:執(zhí)行在哪個(gè)queue上榛丢。

7. 在RACSignal中

在RACSignal也有積極計(jì)算和惰性求值的信號(hào)铲球。


+ (RACSignal *)startEagerlyWithScheduler:(RACScheduler *)scheduler block:(void (^)(id<RACSubscriber> subscriber))block {
    NSCParameterAssert(scheduler != nil);
    NSCParameterAssert(block != NULL);
    
    RACSignal *signal = [self startLazilyWithScheduler:scheduler block:block];
    [[signal publish] connect];
    return [signal setNameWithFormat:@"+startEagerlyWithScheduler: %@ block:", scheduler];
}


startEagerlyWithScheduler中會(huì)調(diào)用startLazilyWithScheduler產(chǎn)生一個(gè)信號(hào)signal,然后緊接著轉(zhuǎn)換成熱信號(hào)晰赞。通過(guò)startEagerlyWithScheduler產(chǎn)生的信號(hào)就直接是一個(gè)熱信號(hào)睬辐。


+ (RACSignal *)startLazilyWithScheduler:(RACScheduler *)scheduler block:(void (^)(id<RACSubscriber> subscriber))block {
    NSCParameterAssert(scheduler != nil);
    NSCParameterAssert(block != NULL);
    
    RACMulticastConnection *connection = [[RACSignal
                                           createSignal:^ id (id<RACSubscriber> subscriber) {
                                               block(subscriber);
                                               return nil;
                                           }]
                                          multicast:[RACReplaySubject subject]];
    
    return [[[RACSignal
              createSignal:^ id (id<RACSubscriber> subscriber) {
                  [connection.signal subscribe:subscriber];
                  [connection connect];
                  return nil;
              }]
             subscribeOn:scheduler]
            setNameWithFormat:@"+startLazilyWithScheduler: %@ block:", scheduler];
}

上述是startLazilyWithScheduler:的源碼實(shí)現(xiàn),在這個(gè)方法中和startEagerlyWithScheduler最大的區(qū)別就出來(lái)了宾肺,connect方法在return的信號(hào)中溯饵,所以Lazily就體現(xiàn)在,通過(guò)startLazilyWithScheduler建立出來(lái)的信號(hào)锨用,只有訂閱它之后才能調(diào)用到connect丰刊,轉(zhuǎn)變成熱信號(hào)。

在這里調(diào)用了subscribeOn:scheduler,這里用到了scheduler。

8. 在NSData+RACSupport中

+ (RACSignal *)rac_readContentsOfURL:(NSURL *)URL options:(NSDataReadingOptions)options scheduler:(RACScheduler *)scheduler {
    NSCParameterAssert(scheduler != nil);
    
    RACReplaySubject *subject = [RACReplaySubject subject];
    [subject setNameWithFormat:@"+rac_readContentsOfURL: %@ options: %lu scheduler: %@", URL, (unsigned long)options, scheduler];
    
    [scheduler schedule:^{
        NSError *error = nil;
        NSData *data = [[NSData alloc] initWithContentsOfURL:URL options:options error:&error];
        if (data == nil) {
            [subject sendError:error];
        } else {
            [subject sendNext:data];
            [subject sendCompleted];
        }
    }];
    
    return subject;
}


在這個(gè)方法中钥弯,會(huì)傳入RACQueueScheduler或者RACTargetQueueScheduler的RACScheduler拣宰。那么調(diào)用schedule方法就會(huì)執(zhí)行到這里:


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


9. 在NSString+RACSupport中

+ (RACSignal *)rac_readContentsOfURL:(NSURL *)URL usedEncoding:(NSStringEncoding *)encoding scheduler:(RACScheduler *)scheduler {
    NSCParameterAssert(scheduler != nil);
    
    RACReplaySubject *subject = [RACReplaySubject subject];
    [subject setNameWithFormat:@"+rac_readContentsOfURL: %@ usedEncoding:scheduler: %@", URL, scheduler];
    
    [scheduler schedule:^{
        NSError *error = nil;
        NSString *string = [NSString stringWithContentsOfURL:URL usedEncoding:encoding error:&error];
        if (string == nil) {
            [subject sendError:error];
        } else {
            [subject sendNext:string];
            [subject sendCompleted];
        }
    }];
    
    return subject;
}


同NSData+RACSupport中的rac_readContentsOfURL: options: scheduler:一樣,也會(huì)傳入RACQueueScheduler或者RACTargetQueueScheduler的RACScheduler隔披。

10. 在NSUserDefaults+RACSupport中

RACScheduler *scheduler = [RACScheduler scheduler];

在這個(gè)方法中也會(huì)新建RACTargetQueueScheduler,一個(gè)Global Dispatch Queue。優(yōu)先級(jí)是RACSchedulerPriorityDefault澄耍。

最后

關(guān)于RACScheduler底層實(shí)現(xiàn)分析都已經(jīng)分析完成。最后請(qǐng)大家多多指教晌缘。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末齐莲,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子磷箕,更是在濱河造成了極大的恐慌选酗,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件岳枷,死亡現(xiàn)場(chǎng)離奇詭異芒填,居然都是意外死亡呜叫,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門殿衰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)怀偷,“玉大人,你說(shuō)我怎么就攤上這事播玖∽倒ぃ” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵蜀踏,是天一觀的道長(zhǎng)维蒙。 經(jīng)常有香客問(wèn)我,道長(zhǎng)果覆,這世上最難降的妖魔是什么颅痊? 我笑而不...
    開(kāi)封第一講書人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮局待,結(jié)果婚禮上斑响,老公的妹妹穿的比我還像新娘。我一直安慰自己钳榨,他們只是感情好舰罚,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著薛耻,像睡著了一般营罢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上饼齿,一...
    開(kāi)封第一講書人閱讀 52,246評(píng)論 1 308
  • 那天饲漾,我揣著相機(jī)與錄音,去河邊找鬼缕溉。 笑死考传,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的证鸥。 我是一名探鬼主播僚楞,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼敌土!你這毒婦竟也來(lái)了镜硕?” 一聲冷哼從身側(cè)響起运翼,我...
    開(kāi)封第一講書人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤返干,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后血淌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體矩欠,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡财剖,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了癌淮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片躺坟。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖乳蓄,靈堂內(nèi)的尸體忽然破棺而出咪橙,到底是詐尸還是另有隱情,我是刑警寧澤虚倒,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布美侦,位于F島的核電站,受9級(jí)特大地震影響魂奥,放射性物質(zhì)發(fā)生泄漏菠剩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一耻煤、第九天 我趴在偏房一處隱蔽的房頂上張望具壮。 院中可真熱鬧,春花似錦哈蝇、人聲如沸棺妓。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)涧郊。三九已至,卻和暖如春眼五,著一層夾襖步出監(jiān)牢的瞬間妆艘,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工看幼, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留批旺,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓诵姜,卻偏偏與公主長(zhǎng)得像汽煮,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子棚唆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 1. GCD簡(jiǎn)介 什么是GCD呢暇赤?我們先來(lái)看看百度百科的解釋簡(jiǎn)單了解下概念 引自百度百科:Grand Centra...
    千尋_544f閱讀 369評(píng)論 0 0
  • iOS中GCD的使用小結(jié) 作者dullgrass 2015.11.20 09:41*字?jǐn)?shù) 4996閱讀 20199...
    DanDanC閱讀 835評(píng)論 0 0
  • 目錄(GCD): 關(guān)鍵詞 混淆點(diǎn) 場(chǎng)景應(yīng)用 總結(jié) 1. 關(guān)鍵詞 線程概念: 獨(dú)立執(zhí)行的代碼段,一個(gè)線程同時(shí)間只能執(zhí)...
    Ryan___閱讀 1,272評(píng)論 0 3
  • 落落說(shuō)忘記一個(gè)人要七年,七年全身細(xì)胞重生一次瞎惫。今年正好是落落分手后的第七年溜腐。 落落是一個(gè)我見(jiàn)過(guò)的所有女孩中最單純的...
    洪木木閱讀 1,141評(píng)論 0 0