reactive-cocoa - 具有源自另一个信号的副作用的信号、多个订阅者

标签 reactive-cocoa

好吧,这个问题让我有点难住了。

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

两步过程是:

  1. 获取 session token
  2. 通过调用 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 属性的后续更改不会触发新的验证请求。

需要明确的是,以下序列按预期工作:

  1. 没有 session token ,isLoggedIn 开头为 NO
  2. authenticated 发出 NO
  3. 用户登录,获取 session token
  4. isLoggedIn 更改为 YES,触发验证请求
  5. 验证请求成功,authentiated 发出 YES

以下序列不起作用:

  1. 存在过期 session token ,isLoggedIn 开头为 YES
  2. 已触发验证请求,但失败
  3. authenticated 发出 NO
  4. 响应此显示登录屏幕,用户登录,获取新的 session token
  5. 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/

相关文章:

ios - 在程序中使用@weakify 时出现错误 unexpected '@'

swift - 信号 : Collect values over time interval

cocoapods - 如何设置 ReactiveCocoa 3.0 podspec 以用作我的测试项目的 'private pod'?

objective-c - 对象的 NSArray 的 RACSignal

reactive-cocoa - 如何从 sendError : on RACSignal 映射错误

ios - 在使用下一个事件之前,如何在 flattenMap block 中等待信号完成?

ios - 如何等到任务完成然后返回一个信号,reactive cocoa

objective-c - 在有限数量的线程上并行订阅 ReactiveCocoa 信号

objective-c - 使用 Reactive Cocoa 通过 REST API 更新服务器数据的最佳实践