好吧,这个问题让我有点难住了。
我有一个两步登录过程,我尝试使用 ReactiveCocoa 对其进行建模,并提供一个信号,让订阅者知道客户端是否已通过身份验证。
两步过程是:
- 获取 session token
- 通过调用 API 端点验证 session token 是否有效
我会尝试简化事情,但我有一个对象,我们称之为UserSession
,它有一个简单的属性isLoggedIn
,如果用户有,则返回YES session token ,如果没有,则为“否”。当在 UserSession
对象上获取和设置 session token 时,该值会发生变化并发出通常的 KVO 通知。如果我只想知道我何时拥有 token ,我可以使用 RACObserve 观察此属性。
我真正想做的是在UserSession
上有一个名为authenticated
的属性,它返回一个RACSignal
。该信号应该:
- 如果 isLoggedIn 更改为 NO,则发出 NO
- 如果 isLoggedIn 更改为 YES 并且验证请求成功,则发出 YES
- 如果 isLoggedIn 更改为 YES 并且验证请求失败,则发出 NO。
一个简单、幼稚的实现如下所示:
- (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
block 中,对其进行多播,然后在 内返回多播信号flattenMap
block 。这种方法有效 - 它可以防止多个验证请求 - 但对 isLoggedIn
属性的后续更改不会触发新的验证请求。
需要明确的是,以下序列按预期工作:
- 没有 session token ,
isLoggedIn
开头为NO
authenticated
发出NO
- 用户登录,获取 session token
isLoggedIn
更改为YES
,触发验证请求- 验证请求成功,
authentiated
发出YES
以下序列不起作用:
- 存在过期 session token ,
isLoggedIn
开头为YES
- 已触发验证请求,但失败
authenticated
发出NO
- 响应此显示登录屏幕,用户登录,获取新的 session token
isLoggedIn
应向其RACObserve
发出另一个YES
并触发另一个验证请求,但这永远不会发生。
有办法实现我想要的吗?
编辑:这是我的多播尝试:
- (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 block 来尝试可视化正在发生的事情:
- (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 中,当登录后设置 session token 时,我从未看到 LOGGED IN 或 AUTH 日志调用。
最佳答案
看来我找到了自己的答案。我对我的多播/重放解决方案都相差不远。问题是,如果连接以某种方式失败,[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;
}
关于reactive-cocoa - 具有源自另一个信号的副作用的信号、多个订阅者,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25893790/