ReactiveCocoa间隔计时器如何产生这种效果?

时间:2014-08-16 09:24:45

标签: reactive-cocoa

我正在学习ReactiveCocoa,并且被以下内容困扰:

如果我将此信号绑定到标签:

-(void)viewDidLoad{
    [super viewDidLoad];
    RAC(self.currentTimeLabel, text) = self.timeAgoSignal;
}

-(RACSignal *)timeAgoSignal{
    return [[[RACSignal interval:1 onScheduler:[RACScheduler scheduler]] startWith:[NSDate date]] map:^id (NSDate *value) {
        NSLog (@"Value: %@", value);
        return value.description;
    }];
}

首先,标签没有更新(我不关心这个,我可能会解释为什么)。我的主要问题是:块如何在每个刻度上记录更新日期?

Value: 2014-08-16 09:15:40 +0000
Value: 2014-08-16 09:15:41 +0000
Value: 2014-08-16 09:15:42 +0000
Value: 2014-08-16 09:15:43 +0000

这对我来说太神奇了。如果我有一个计时器输出[NSDate日期],它当然不会在每个刻度上改变:

-(instancetype)init{
    if (self = [super init]) {
        _date = [NSDate date];

        [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector (output) userInfo:nil repeats:YES];
    }
    return self;
}
-(void)output{
    NSLog (@"Date: %@", self.date);
}

输出:

Date: 2014-08-16 09:19:36 +0000
Date: 2014-08-16 09:19:36 +0000
Date: 2014-08-16 09:19:36 +0000
Date: 2014-08-16 09:19:36 +0000

这是预期的行为。

如果startWith:[NSDate date]位于每个刻度线上重新调用的块中,那么我可以理解。但事实并非如此:

例如,如果我改为使用startWith:[self date],我可以看到[self date]只被调用一次,因此每次都不会像预期的那样神奇地调用该方法。

我看不到手动递增日期的代码。那怎么发生呢?

1 个答案:

答案 0 :(得分:2)

好问题。让我们拆开你的代码吧。

如果您要将日志记录代码略微增加到以下内容:

NSLog (@"Value(%p): %@", value, value);

然后你会看到value对象的指针。每次涉及块时,都会生成一个新的NSDate实例并将其作为值发送。毕竟,NSDate个对象是不可变的。

好的,等等你的问题:这是怎么回事?好吧,ReactiveCocoa的好处在于它的开源,所以我们不必猜测它是怎么回事。设置重复的code实际上使用了您传入的调度程序。让我们看一下that code

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

因此,调度程序只使用GCD,以便每次都使用新的NSDate实例设置重复的块调用。

所以最后一个难题是使用startWith:interval:scheduler:设置一个信号,在interval秒后触发其第一个事件。在此之前,信号没有发送任何内容,文本字段的值将为nil。所以我们使用startWith:来启动信号并让它立即发送一个值。