RAC中的定时器
这次我们写一个demo来讲解RAC中的定时器的使用。
demo功能:点击button发送验证码,并开始倒计时,倒计时结束之前不允许再次点击button发送验证码。
上面是一个button,下面是一个textview,为的是验证滚动textview的时候,是不是影响timer的计时。
@interface ViewController ()
@property(nonatomic,weak) IBOutlet UIButton * btn;
@property(nonatomic,assign) NSInteger time;
@property(nonatomic,retain) RACDisposable * dispoable;
@end
把创建的button链接到btn属性,另外添加要用到的另外2个属性。然后把下面的代码放到
viewDidLoad的最后面。
@weakify(self)
[[_btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
@strongify(self)
x.enabled = false;
self.time = 10;
//这个就是RAC中的GCD
self.dispoable = [[RACSignal interval:1.0 onScheduler:[RACScheduler mainThreadScheduler]] subscribeNext:^(NSDate * _Nullable x) {
_time --;
NSString * title = _time > 0 ? [NSString stringWithFormat:@"请等待 %d 秒后重试",_time] : @"发送验证码";
[self.btn setTitle:title forState:UIControlStateNormal | UIControlStateDisabled];
self.btn.enabled = (_time==0)? YES : NO;
if (_time == 0) {
[self.dispoable dispose];
}
}];
}];
点击运行,结果没有问题,拖动textview的滚动条,计时器依然在倒计时。
代码在一处就实现了所有功能。下面看看实现原理。
先说一下下面这行代码的作用吧
[[_btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
NSLog(@"Hello RAC!");
}];
这个代码的作用是创建了一个信号,并且订阅了这个信号。等按钮点击了,这个block就会执行。具体的代码我们下一篇文章再分析吧!这里就知道我们点击按钮这个block会执行就OK。进入block里面,主要就是又创建了一个信号,下面我们通过代码看都实现了什么功能。
[[RACSignal interval:1.0 onScheduler:[RACScheduler mainThreadScheduler]] subscribeNext:^(NSDate * _Nullable x) {
_time --;
NSString * title = _time > 0 ? [NSString stringWithFormat:@"请等待 %d 秒后重试",_time] : @"发送验证码";
[self.btn setTitle:title forState:UIControlStateNormal | UIControlStateDisabled];
self.btn.enabled = (_time==0)? YES : NO;
if (_time == 0) {
[self.dispoable dispose];
}
}];
继续深入interval:onScheduler:方法的定义之前,看一下这个方法的第二个参数,生成方法如下,它是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;
}
然后进入interval:onScheduler:方法
+ (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler {
return [[RACSignal interval:interval onScheduler:scheduler withLeeway:0.0] setNameWithFormat:@"+interval: %f onScheduler: %@", (double)interval, scheduler];
}
这个方法就是简单的调用了下面的方法。下面的方法就是创建了一个信号,最终调用的是createSignal方法。当然这个方法需要传递一个block,这个之前关于RAC的文章里有说明,这个block会在subscribeNext:方法调用的时候执行。
+ (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler withLeeway:(NSTimeInterval)leeway {
NSCParameterAssert(scheduler != nil);
NSCParameterAssert(scheduler != RACScheduler.immediateScheduler);
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
return [scheduler after:[NSDate dateWithTimeIntervalSinceNow:interval] repeatingEvery:interval withLeeway:leeway schedule:^{
[subscriber sendNext:[NSDate date]];
}];
}] setNameWithFormat:@"+interval: %f onScheduler: %@ withLeeway: %f", (double)interval, scheduler, (double)leeway];
}
继续进入方法after: repeatingEvery: withLeeway: schedule:,这个方法最后一个参数是一个block,用来发送信号[subscriber sendNext:[NSDate date]];,只有发送了信号subscribeNext:后面的block才会执行。这个方法的返回值是一个RACDisposable对象,这个前面的文章里也说了,就是createSignal:传递的block参数的返回值。在这个方法里,其实就是启动了一个GCD的定时器。定时器执行block,这个block就包含一个语句
[subscriber sendNext:[NSDate date]];就是发送时间事件。这样就触发了subscribeNext:后面的block。
- (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);
}];
}
上面的方法启动了一个定时器,并且返回了一个RACDisposable对象。一订阅这个定时器就启动,通过[subscriber sendNext:[NSDate date]];定时发送消息执行subscribeNext后面的代码。