Ответ 1
Если есть сомнения, проверьте RACSignal+Operations.h а также RACStream.h, потому что он должен быть оператором для того, что вы хотите сделать. В этом случае, основной недостающий кусок - scanWithStart: уменьшить:.
Прежде всего, давайте посмотрим на baseSignal
. Логика останется
в основном то же самое, за исключением того, что мы должны опубликовать a
соединение
для него:
RACMulticastConnection *timer = [[[RACSignal
interval:0.05 onScheduler:[RACScheduler mainThreadScheduler]]
take:percentRemaining]
publish];
Это значит, что мы можем использовать один таймер между всеми зависимыми
сигналы. Хотя baseSignal
, который вы предоставили, также будет работать,
воссоздать таймер для каждого абонента (включая зависимые сигналы), который может
приводят к крошечным отклонениям в их стрельбе.
Теперь мы можем использовать -scanWithStart:reduce:
для увеличения countLabel
и уменьшите значение progressView
. Этот оператор принимает предыдущие результаты и
текущее значение, и позволяет нам преобразовывать или комбинировать их, но мы хотим.
В нашем случае мы просто хотим игнорировать текущее значение (отправлено NSDate
на +interval:
), поэтому мы можем просто манипулировать предыдущим:
RAC(self.countLabel, text) = [[[timer.signal
scanWithStart:@0 reduce:^(NSNumber *previous, id _) {
return @(previous.unsignedIntegerValue + 1);
}]
startWith:@0]
map:^(NSNumber *count) {
return count.stringValue;
}];
RAC(self.progressView, progress) = [[[timer.signal
scanWithStart:@(percentRemaining) reduce:^(NSNumber *previous, id _) {
return @(previous.unsignedIntegerValue - 1);
}]
startWith:@(percentRemaining)]
map:^(NSNumber *percent) {
return @(percent.unsignedIntegerValue / 100.0);
}];
Операторы -startWith:
в приведенном выше примере могут казаться избыточными, но это
необходимо убедиться, что text
и progress
установлены до timer.signal
послал что-нибудь.
Затем мы просто используем обычную подписку для завершения. Это полностью возможно, что эти побочные эффекты также могут быть превращены в сигналы, но это трудно узнать, не видя кода:
[timer.signal subscribeCompleted:^{
// Move along...
}];
Наконец, поскольку мы использовали a RACMulticastConnection
выше, на самом деле ничего не будет
огонь еще. Подключения должны запускаться вручную:
[timer connect];
Это связывает все приведенные выше подписки и выключает таймер, поэтому значения начинают течь к свойствам.
Теперь это, очевидно, больше кода, чем императивный эквивалент, поэтому можно спросите, почему это стоит. Существует несколько преимуществ:
- Теперь вычисления значений поточно-безопасные, потому что они не зависят от стороны последствия. Если вам нужно реализовать что-то более дорогое, это очень просто для перемещения важной работы в фоновый поток.
- Аналогично, вычисления значений независимы друг от друга. Oни могут быть легко распараллелены, если это когда-нибудь станет ценным.
- Вся логика теперь локальна для привязок. Вам не нужно удивляться где происходят изменения или беспокоиться о заказе (например, между инициализация и обновление), поскольку все это в одном месте и может быть прочитано сверху вниз.
- Значения могут быть рассчитаны без ссылки на представление. Для например, в Model-View-ViewModel, счет и прогресс будут фактически определены в представлении модель, а затем уровень представления - это всего лишь набор немых привязок.
- Измененные значения передаются из только одного входа. Если вам вдруг понадобится включить другой источник входного сигнала (например, реальный прогресс вместо таймера), там только одно место, которое вам нужно изменить.
В принципе, это классический пример императивного и функционального программирования.
Хотя императивный код может начинаться с меньшей сложности, он растет по сложности экспоненциально. Функциональный код (и особенно функциональный реактивный код) может начинают сложнее, но тогда его сложность растет линейно - это много легче управлять, поскольку приложение растет.