具有副作用的信号来自另一个信号,多个订户

时间:2014-09-17 14:48:16

标签: reactive-cocoa

好的,这个让我有些难过。

我有一个两步登录过程,我尝试使用ReactiveCocoa进行建模,并提供一个信号,让订阅者知道客户端是否经过身份验证。

两步过程是:

  1. 获取会话令牌
  2. 通过调用API端点验证会话令牌的工作原理
  3. 我会尝试简化一些事情,但我有一个对象,我们称之为UserSession,它有一个简单的属性isLoggedIn,如果用户有一个会话令牌,则返回YES不,如果它没有。当在UserSession对象上获取并设置会话令牌时,此值会更改并发出通常的KVO通知。如果我只想知道何时有令牌,我可以使用RACObserve来观察此属性。

    我真正想做的是在名为UserSession的{​​{1}}上添加一个返回authenticated的属性。这个信号应该:

    • 如果isLoggedIn更改为NO
    • ,则发出NO
    • 如果isLoggedIn更改为YES且验证请求成功,则发出YES
    • 如果isLoggedIn更改为YES并且验证请求失败,则发出NO。

    一个简单,天真的实现看起来像这样:

    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属性的后续更改不会触发新的验证请求。

    为清楚起见,以下序列按预期工作:

    1. 没有会话令牌,isLoggedInisLoggedIn
    2. 开头
    3. NO发出authenticated
    4. 用户登录,获取会话令牌
    5. NO更改为isLoggedIn,触发验证请求
    6. 验证请求成功,YES发出authenticated
    7. 以下序列不起作用:

      1. 已过期的会话令牌,YESisLoggedIn
      2. 开头
      3. 触发验证请求,失败
      4. YES发出authenticated
      5. 响应此时显示的登录屏幕,用户登录,获取新的会话令牌
      6. NO应向其isLoggedIn发出另一个YES并触发另一个验证请求,但这绝不会发生。
      7. 有没有办法实现我想要的目标?

        编辑:这是我的多播尝试:

        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日志调用。

1 个答案:

答案 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;
}