I'm learning ReactiveCocoa, and am stumped by the following:
If I bind this signal to a label:
-(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;
}];
}
First of all, the label doesn't update (I'm not concerned about this, I can probably work out why). My main question is: how does the block log an updated date on each tick?
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
This is amazing to me. If I had a timer output an [NSDate date], it would of course not change on each tick:
-(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);
}
Output:
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
Which is the expected behaviour.
If the startWith:[NSDate date]
was in a block which was called anew on each tick, then I could understand. But it's not:
For instance, if I instead use startWith:[self date]
, I can see that [self date]
is called only once, so it's not magically calling that method each time, again as expected.
No code that I can see is incrementing the date manually. So how is this occurring?
Good question. Let's take apart your code.
If you were to augment your logging code slightly to the following:
NSLog (@"Value(%p): %@", value, value);
Then what you would see is the pointer of the value
object. Every time the block is involved, a new NSDate
instance is being generated and sent as a value. NSDate
objects are immutable, after all.
OK, so on to your question: how is this happening? Well, the benefit of ReactiveCocoa is that its open source, so we don't have to guess what's going on. The code that sets up the repetition actually uses the scheduler you pass in. Let's take a look at 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);
}];
So the scheduler just uses GCD in order to set up recurring block invocations with new NSDate
instances every time.
So the last piece of the puzzle is the use of startWith:
. interval:scheduler:
sets up a signal that fires its first event after interval
seconds. Until then, the signal hasn't sent anything and the value of your text field would be nil
. So we use startWith:
in order to kick off the signal and get it to send a value immediately.