我有一个两步登录过程,我尝试使用ReactiveCocoa进行建模,并提供一个信号,让订阅者知道客户端是否经过身份验证。
两步过程是:
我会尝试简化一些事情,但我有一个对象,我们称之为UserSession
,它有一个简单的属性isLoggedIn
,如果用户有一个会话令牌,则返回YES不,如果它没有。当在UserSession
对象上获取并设置会话令牌时,此值会更改并发出通常的KVO通知。如果我只想知道何时有令牌,我可以使用RACObserve
来观察此属性。
我真正想做的是在名为UserSession
的{{1}}上添加一个返回authenticated
的属性。这个信号应该:
一个简单,天真的实现看起来像这样:
RACSignal
这种方法的问题是每个订阅者都会触发验证请求 - 我只想在- (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
这似乎与使用较少代码但行为与上述相同的行为大致相同。我已经添加了do块来尝试可视化正在发生的事情:
- (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;
}
在上面的方案2中,登录后设置会话令牌时,我从未看到LOGGED IN或AUTH日志调用。
答案 0 :(得分:3)
看起来我找到了自己的答案。我的任何多播/重播解决方案都不远了。问题是,如果连接以某种方式失败,[self verifySessionToken]
返回的信号将发送错误,这会破坏整个事情。
我可以通过发送@NO而不是错误来解决这个问题,但我决定保留原样,并使错误处理显式化。
我还发现在外部信号上使用重放比在内部信号上使用多播更优雅。
这是我最后的工作解决方案:
- (RACSignal *)authenticated
{
if (_authenticated == nil) {
@weakify(self);
_authenticated = [[RACObserve(self, isLoggedIn) flattenMap:^id(NSNumber *isLoggedIn) {
@strongify(self);
if (isLoggedIn.boolValue) {
return [[self verifySessionToken] catch:^RACSignal *(NSError *error) {
DDLogError(@"Error verifying session token");
return [RACSignal return:@NO];
}];
}
return [RACSignal return:@NO];
}] replay];
}
return _authenticated;
}