RACUnit和RACScheduler

RACUnit

RACUnit类中只有一个单例

@interface RACUnit : NSObject

/// A singleton instance.
+ (RACUnit *)defaultUnit;

@end

RACScheduler

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

先来看下头文件

首先是创建方法:

//返回一个同步执行的队列
+ (RACScheduler *)immediateScheduler;

//返回一个RACTargetQueueScheduler队列
+ (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority name:(nullable NSString *)name;

//返回一个RACTargetQueueScheduler队列,默认名字为org.reactivecocoa.ReactiveObjC.RACScheduler.backgroundScheduler
+ (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority;

//返回一个主队列,是一个单例
+ (RACScheduler *)mainThreadScheduler;

//返回一个优先级为普通的队列
+ (RACScheduler *)scheduler;

//返回当先队列
+ (nullable RACScheduler *)currentScheduler;

然后是方法:

//当前队列异步执行方法
- (nullable RACDisposable *)schedule:(void (^)(void))block;
//在某个时间点后执行方法
- (nullable RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block;

//延时执行block
- (nullable RACDisposable *)afterDelay:(NSTimeInterval)delay schedule:(void (^)(void))block;
//封装了一个GCD定时器,参数意义分别是,1.第一次开始执行任务的时间,2.每个多少秒循环一次,3.进入后台后的误差范围,4.执行的任务
- (nullable RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block;
//递归block
- (nullable RACDisposable *)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock;

RACScheduler主要是通过不同的子类来实现其功能的,子类需要自己实现:

    • (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

三个方法。

scheduleRecursiveBlock

其中父类实现了scheduleRecursiveBlock用来实现递归:



- (RACDisposable *)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock {
    RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];

    [self scheduleRecursiveBlock:[recursiveBlock copy] addingToDisposable:disposable];
    return disposable;
}

首先,先创建一个销毁任务,并将传入的recursiveBlock拷贝一份然后调用scheduleRecursiveBlock:addingToDisposable:方法,并返回销毁任务。(block没有任何参数返回)。接下来,看调用的这方法:

RACCompoundDisposable *selfDisposable = [RACCompoundDisposable compoundDisposable];
        [disposable addDisposable:selfDisposable];

先创建一个聚合任务,并将它加到刚才创建的销毁任务里。

此block会重复调用scheduleRecursiveBlock方法。

这段代码设计很巧妙,兼顾了异步与同步,先来看下示例代码:

//例一、
[[RACScheduler mainThreadScheduler] scheduleRecursiveBlock:^(void (^ _Nonnull reschedule)(void)) {
        a++;
        NSLog(@"%ld",a);
        
        if (a < 10) {
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                reschedule();
            });
        }
        
  }];
  
//例二、
   
    __block NSInteger a = 0;
    [[RACScheduler mainThreadScheduler] scheduleRecursiveBlock:^(void (^ _Nonnull reschedule)(void)) {
        a++;
        NSLog(@"%ld",a);
        if (a < 10) {
            reschedule();
        }
        
    }];

这两段代码唯一区别就是reschedule时机,一个延时两秒,一个立刻执行。具体的实现是在调用方法的时候这个reschedule是一个block块,在调用recursiveBlock回调的时候创建

typedef void (^RACSchedulerRecursiveBlock)(void (^reschedule)(void));
void (^reallyReschedule)(void) = ^{
                if (disposable.disposed) return;
                [self scheduleRecursiveBlock:recursiveBlock addingToDisposable:disposable];
            };
__block NSUInteger rescheduleCount = 0;//用来技术,当前未执行block的数量
__block BOOL rescheduleImmediately = NO;//是否立刻执行

@autoreleasepool {
    recursiveBlock(^{
        [lock lock];
        BOOL immediate = rescheduleImmediately;
        if (!immediate) ++rescheduleCount;
        [lock unlock];

        if (immediate) reallyReschedule();
    });
}
[lock lock];
NSUInteger synchronousCount = rescheduleCount;
rescheduleImmediately = YES;
[lock unlock];

for (NSUInteger i = 0; i < synchronousCount; i++) {
    reallyReschedule();
}


这里有个属性,是否立刻执行,默认为NO,如果没有调用或者直接调用这个reschedule,那么这个值为NO,最终会走入:

if (!immediate) ++rescheduleCount;

方法使计数加1,最终在下面的for循环中执行reallyReschedule;若是在异步调用的话,那么rescheduleImmediately会在调用block之前变为YES,引用计数加一也会跳过,如果过段时间调用reschedule那么会走入:

if (immediate) reallyReschedule();

方法调用这个block。这个block会重新调用scheduleRecursiveBlock,从而实现递归。

performAsCurrentScheduler

这个方法是由父类实现,是为了来实现currentScheduler方法,可以很方便的获取到当前的队列

- (void)performAsCurrentScheduler:(void (^)(void))block {
    NSCParameterAssert(block != NULL);
   //获取当前队列
    RACScheduler *previousScheduler = RACScheduler.currentScheduler;
    //设置当前队列为自身NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = self;
  //执行block回调
    @autoreleasepool {
        block();
    }
//block执行完后重新将队列设为之前的,如果之前没有就移除
    if (previousScheduler != nil) {
        NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = previousScheduler;
    } else {
        [NSThread.currentThread.threadDictionary removeObjectForKey:RACSchedulerCurrentSchedulerKey];
    }
}

RACImmediateScheduler

RACImmediateScheduler是会直接执行block,立即执行调度的任务,这是唯一一个支持同步执行的调度器,他重写了父类的三个方法:

//直接回调
- (RACDisposable *)schedule:(void (^)(void))block {
    NSCParameterAssert(block != NULL);

    block();
    return nil;
}

//当前线程等待N秒后回调block
- (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block {
    NSCParameterAssert(date != nil);
    NSCParameterAssert(block != NULL);

    [NSThread sleepUntilDate:date];
    block();

    return nil;
}
//不支持repeat
- (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;
}

//重写递归,不支持异步
- (RACDisposable *)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock {
for (__block NSUInteger remaining = 1; remaining > 0; remaining--) {
        recursiveBlock(^{
            remaining++;
        });
    }
}

RACQueueScheduler

异步调度队列,最主要的队列,里面实现了:

- (RACDisposable *)schedule:(void (^)(void))block

实现了父类的CurrentSchedule。

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

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

实现了延时一段时间执行

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

使用了GCD定时器,参数分别传,第一次执行的时间,执行时间间隔,精确度,执行的任务

RACSubscriptionScheduler

这个类只是做了一个转化:如果当前有队列,使用当前队列进行任务,如果没有使用默认队列执行任务。

RACTargetQueueScheduler

RACQueueScheduler的子类,设置线程间的优先级,
dispatch_set_target_queue(queue, targetQueue);
使用这个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");
    });


结果


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一致。

注意:同步队列和异步队列的区别

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,923评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,154评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,775评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,960评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,976评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,972评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,893评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,709评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,159评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,400评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,552评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,265评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,876评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,528评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,701评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,552评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,451评论 2 352

推荐阅读更多精彩内容