Хорошо, это меня немного озадачило.
У меня есть двухэтапный процесс входа в систему, который я пытаюсь смоделировать с помощью ReactiveCocoa и предоставить сигнал, который позволяет подписчикам узнать, аутентифицирован ли клиент.
Двухэтапный процесс:
- Получить токен сеанса
- Убедитесь, что токен сеанса работает, вызвав конечную точку API.
Я попытаюсь все упростить, но у меня есть объект, назовем его UserSession
, который имеет простое свойство isLoggedIn
, которое возвращает YES, если у пользователя есть токен сеанса, и NO, если его нет. Это значение изменяется и выдает обычные уведомления KVO, когда токен сеанса извлекается и устанавливается для объекта UserSession
. Я могу наблюдать за этим свойством, используя RACObserve
, если я просто хочу знать, когда у меня есть токен.
Что я действительно хочу сделать, так это иметь свойство UserSession
с именем authenticated
, которое возвращает RACSignal
. Этот сигнал должен:
- Выдать НЕТ, если isLoggedIn изменится на НЕТ
- Выдать YES, если isLoggedIn изменится на YES и запрос на проверку выполнен успешно.
- Выдайте NO, если isLoggedIn изменится на YES и запрос на проверку завершится неудачно.
Простая наивная реализация выглядит так:
- (RACSignal *)authenticated
{
if (_authenticated == nil) {
_authenticated = [RACObserve(self, isLoggedIn) flattenMap:^id(NSNumber *isLoggedIn) {
if (isLoggedIn.boolValue) {
// does the async HTTP request, wrapped up in a signal that emits YES/NO, or error
// then completes.
return [self verifySessionToken];
}
return [RACSignal return:@NO];
}];
}
return _authenticated;
}
Проблема с этим подходом заключается в том, что запрос проверки будет отправлен для каждого подписчика — я хочу, чтобы был отправлен только один запрос проверки для одного изменения в свойстве isLoggedIn
.
Я пытался использовать многоадресное соединение, заключая [self doVerificationRequest]
в блок defer
, выполняя многоадресную рассылку, а затем возвращая многоадресный сигнал внутри блока flattenMap
. Такой способ работает — он предотвращает множественные запросы проверки — но последующие изменения свойства isLoggedIn
не вызывают новый запрос проверки.
Чтобы было ясно, следующая последовательность работает, как и ожидалось:
- Нет токена сеанса,
isLoggedIn
начинается сNO
authenticated
излучаетNO
- Пользователь входит в систему, получает токен сеанса
isLoggedIn
меняется наYES
, инициирует запрос подтверждения- Запрос проверки выполнен успешно,
authenticated
выдаетYES
Следующая последовательность не работает:
- Присутствует токен сеанса с истекшим сроком действия,
isLoggedIn
начинается сYES
- Запрос проверки запущен, он не работает
authenticated
излучаетNO
- В ответ на это отображается экран входа в систему, пользователь входит в систему, получает новый токен сеанса.
isLoggedIn
должен передать еще одинYES
своемуRACObserve
и вызвать еще один запрос на проверку, но этого никогда не происходит.
Есть ли способ добиться того, чего я хочу здесь?
Изменить: это была моя попытка многоадресной рассылки:
- (RACSignal *)authenticated
{
if (_authenticated == nil) {
RACSignal *deferredVerification = [RACSignal defer:^RACSignal *{
return [self verifySessionToken];
}];
self.tokenVerificationConnection = [deferredVerification publish];
_authenticated = [RACObserve(self, isLoggedIn) flattenMap:^id(NSNumber *isLoggedIn) {
if (isLoggedIn.boolValue) {
return [self.tokenVerificationConnection autoconnect];
}
return [RACSignal return:@NO];
}];
}
return _authenticated;
}
Это также, по-видимому, ведет себя в основном так же, с меньшим количеством кода, но с тем же поведением, что и выше. Я добавил блоки do, чтобы попытаться визуализировать происходящее:
- (RACSignal *)authenticated
{
if (_authenticated == nil) {
@weakify(self);
_authenticated = [[[[RACObserve(self, isLoggedIn) doNext:^(id x) {
NSLog(@"LOGGED IN %@", x);
}] flattenMap:^id(NSNumber *isLoggedIn) {
@strongify(self);
if (isLoggedIn.boolValue) {
return [self verifySessionToken];
}
return [RACSignal return:@NO];
}] doNext:^(id x) {
NSLog(@"AUTH: %@", x);
}] replay];
}
return _authenticated;
}
В приведенном выше сценарии 2 я никогда не вижу вызовов журнала LOGGED IN или AUTH, когда токен сеанса устанавливается после входа в систему.