Reactive cocoa 由另一个信号衍生的具有副作用的信号,多个用户
好吧,这件事让我有点困惑 我有一个两步登录过程,我试图使用ReactiveCocoa对其进行建模,并提供一个信号,让订阅者知道客户端是否经过身份验证 这两个步骤是:Reactive cocoa 由另一个信号衍生的具有副作用的信号,多个用户,reactive-cocoa,Reactive Cocoa,好吧,这件事让我有点困惑 我有一个两步登录过程,我试图使用ReactiveCocoa对其进行建模,并提供一个信号,让订阅者知道客户端是否经过身份验证 这两个步骤是: 获取会话令牌 通过调用API端点验证会话令牌是否有效 我将尝试简化事情,但我有一个对象,让我们称它为UserSession,它有一个简单的属性isLoggedIn,如果用户有会话令牌,它将返回YES,如果没有则返回NO。当在UserSession对象上获取并设置会话令牌时,该值会更改并发出通常的KVO通知。如果我只想知道何时有令牌,
UserSession
,它有一个简单的属性isLoggedIn
,如果用户有会话令牌,它将返回YES,如果没有则返回NO。当在UserSession
对象上获取并设置会话令牌时,该值会更改并发出通常的KVO通知。如果我只想知道何时有令牌,我可以使用RACObserve
观察此属性
我真正想做的是在UserSession
上有一个名为authenticated
的属性,它返回一个RACSignal
。该信号应:
- 如果isLoggedIn更改为“否”,则发出“否”
- 如果isLoggedIn更改为YES且验证请求成功,则发出YES
- 如果isLoggedIn更改为是且验证请求失败,则发出否
- (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]
包装在延迟
块中,对其进行多播,然后在扁平映射
块中返回多播信号。这种类型的工作方式可以防止多个验证请求,但是对isLoggedIn
属性的后续更改不会触发新的验证请求
需要说明的是,以下顺序按预期工作:
isLoggedIn
以No
authenticated
发出NO
isLoggedIn
更改为YES
,将触发验证请求authenticated
发出YES
isLoggedIn
以YES
authenticated
发出NO
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块来尝试和可视化正在发生的事情:
- (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中,在登录后设置会话令牌时,我从未看到登录或身份验证日志调用。看来我找到了自己的答案。我的多播/重播解决方案都离我不远了。问题是,
[self verifySessionToken]
返回的信号会在连接以某种方式失败时发送一个错误,这会破坏整个连接
我本可以通过让它发送@NO而不是一个错误来修复这个问题,但我决定保持原样,并明确错误处理
我还发现在外部信号上使用replay比在内部信号上使用多播更优雅
这是我最后的工作解决方案:
- (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;
}