Reactive cocoa 由另一个信号衍生的具有副作用的信号,多个用户

Reactive cocoa 由另一个信号衍生的具有副作用的信号,多个用户,reactive-cocoa,Reactive Cocoa,好吧,这件事让我有点困惑 我有一个两步登录过程,我试图使用ReactiveCocoa对其进行建模,并提供一个信号,让订阅者知道客户端是否经过身份验证 这两个步骤是: 获取会话令牌 通过调用API端点验证会话令牌是否有效 我将尝试简化事情,但我有一个对象,让我们称它为UserSession,它有一个简单的属性isLoggedIn,如果用户有会话令牌,它将返回YES,如果没有则返回NO。当在UserSession对象上获取并设置会话令牌时,该值会更改并发出通常的KVO通知。如果我只想知道何时有令牌,

好吧,这件事让我有点困惑

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

这两个步骤是:

  • 获取会话令牌
  • 通过调用API端点验证会话令牌是否有效
  • 我将尝试简化事情,但我有一个对象,让我们称它为
    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;
    }