Как интервальный таймер ReactiveCocoa производит этот эффект?

Я изучаю 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] вызывается только один раз, так что это не волшебный вызов этого метода каждый раз, как и ожидалось.

Никакой код, который я вижу, не увеличивает дату вручную. Итак, как это происходит?


person Ian Dundas    schedule 16.08.2014    source источник
comment
Я фактически исправил не обновляющий бит метки: мне нужно было добавить deliverOn:[RACScheduler mainThreadScheduler] к сигналу, чтобы пользовательский интерфейс действительно перерисовывался. Подробнее в нижней части этого сообщения   -  person Ian Dundas    schedule 16.08.2014


Ответы (1)


Хороший вопрос. Давайте разберем ваш код.

Если бы вы немного увеличили свой код ведения журнала до следующего:

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

Тогда вы увидите указатель на объект value. Каждый раз, когда задействован блок, создается новый экземпляр NSDate, который отправляется как значение. В конце концов, NSDate объекты неизменяемы.

Хорошо, так что на ваш вопрос: как это происходит? Что ж, преимущество ReactiveCocoa в том, что его исходный код открыт, поэтому нам не нужно гадать, что происходит. код, который устанавливает повторение фактически использует планировщик, который вы передаете. Давайте посмотрим на этот код.

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:, чтобы запустить сигнал и немедленно отправить значение.

person Ash Furrow    schedule 16.08.2014
comment
ах ха! Поскольку я был так сбит с толку, я неправильно сформулировал свой вопрос, но последняя часть вашего ответа заставила его щелкнуть. Я ошибочно предположил, что каким-то образом объект [NSDate date] сохранялся и увеличивался (и, как вы говорите, они должны быть неизменяемыми), и я не мог найти код для этого. Чего я не учел, так это того, что NSDate*, прошедшее через map:, на самом деле исходило от interval:onScheduler: (хотя теперь очевидно: это должно быть целью. Я думал, что это всего лишь таймер), а параметр startupWith: просто загружает начальное значение. Спасибо, Эш. - person Ian Dundas; 16.08.2014