如何在进行API调用之前使用ReactiveCocoa进行透明身份验证?

bva*_*een 25 cocoa reactive-programming reactive-cocoa

我在一个应用程序中使用ReactiveCocoa,该应用程序调用远程Web API.但在从给定的API主机检索任何内容之前,应用程序必须提供用户的凭据并检索API令牌,然后用于签署后续请求.

我想抽象出这个身份验证过程,以便每当我进行API调用时它都会自动发生.假设我有一个包含用户凭据的API客户端类.

// getThing returns RACSignal yielding the data returned by GET /thing.
// if the apiClient instance doesn't already have a token, it must
// retrieve one before calling GET /thing 
RAC(self.thing) = [apiClient getThing]; 
Run Code Online (Sandbox Code Playgroud)

如何使用ReactiveCocoa透明地导致API的第一个(也是唯一的)请求检索,并且作为副作用,在发出任何后续请求之前安全地存储API令牌?

我还可以使用combineLatest:(或类似的)启动多个同时发出的请求,并且它们都会隐式等待检索令牌.

RAC(self.tupleOfThisAndThat) = [RACSignal combineLatest:@[ [apiClient getThis], [apiClient getThat]]];
Run Code Online (Sandbox Code Playgroud)

此外,如果在进行API调用时检索令牌请求已经在飞行中,则该API调用必须等到检索令牌请求完成.

我的部分解决方案是:

基本模式将用于将flattenMap:产生令牌的信号映射到给定令牌执行所需请求并产生API调用结果的信号.

假设一些方便的扩展NSURLRequest:

- (RACSignal *)requestSignalWithURLRequest:(NSURLRequest *)urlRequest {
    if ([urlRequest isSignedWithAToken])
        return [self performURLRequest:urlRequest];

    return [[self getToken] flattenMap:^ RACSignal * (id token) {
        NSURLRequest *signedRequest = [urlRequest signedRequestWithToken:token];
        assert([urlRequest isSignedWithAToken]);
        return [self requestSignalWithURLRequest:signedRequest];
    }
}
Run Code Online (Sandbox Code Playgroud)

现在考虑订阅的实现-getToken.

  • 在简单的情况下,当已经检索到令牌时,订阅立即产生令牌.
  • 如果尚未检索到令牌,则订阅将延迟到返回令牌的身份验证API调用.
  • 如果身份验证API调用正在进行中,则可以安全地添加另一个观察者,而不会导致通过线路重复进行身份验证API调用.

但是我不知道该怎么做.此外,如何以及在何处安全存储令牌?某种持久/可重复的信号?

Jus*_*ers 45

所以,这里有两件大事:

  1. 您想要共享一些副作用(在这种情况下,获取令牌),而不是每次有新订阅者时重新触发它们.
  2. -getToken无论如何,您希望任何订阅者都能获得相同的值.

为了分享副作用(上面的#1),我们将使用RACMulticastConnection.就像文档说的那样:

多播连接封装了向许多订户共享信号订阅的想法.如果对基础信号的订阅涉及副作用或不应多次调用,则通常需要这样做.

让我们在API客户端类中添加其中一个作为私有属性:

@interface APIClient ()
@property (nonatomic, strong, readonly) RACMulticastConnection *tokenConnection;
@end
Run Code Online (Sandbox Code Playgroud)

现在,这将解决N个当前订户的情况,这些订户都需要相同的未来结果(等待请求令牌在飞行中的API调用),但我们仍然需要其他东西来确保未来订户获得相同的结果(已经 - 获取令牌),无论何时订阅.

这就是RACReplaySubject的用途:

重播主题保存发送的值(达到其定义的容量)并将其重新发送给新订户.它还将重播错误或完成.

为了将这两个概念结合在一起,我们可以使用RACSignal的-multicast:方法,它通过使用特定类型的主题将正常信号转换为连接.

我们可以在初始化时连接大多数行为:

- (id)init {
    self = [super init];
    if (self == nil) return nil;

    // Defer the invocation of -reallyGetToken until it's actually needed.
    // The -defer: is only necessary if -reallyGetToken might kick off
    // a request immediately.
    RACSignal *deferredToken = [RACSignal defer:^{
        return [self reallyGetToken];
    }];

    // Create a connection which only kicks off -reallyGetToken when
    // -connect is invoked, shares the result with all subscribers, and
    // pushes all results to a replay subject (so new subscribers get the
    // retrieved value too).
    _tokenConnection = [deferredToken multicast:[RACReplaySubject subject]];

    return self;
}
Run Code Online (Sandbox Code Playgroud)

然后,我们实现-getToken懒惰地触发获取:

- (RACSignal *)getToken {
    // Performs the actual fetch if it hasn't started yet.
    [self.tokenConnection connect];

    return self.tokenConnection.signal;
}
Run Code Online (Sandbox Code Playgroud)

之后,订阅-getToken(例如-requestSignalWithURLRequest:)结果的任何内容都将获得令牌(如果尚未获取),必要时开始获取,或者等待有空的请求(如果有).

  • 如何处理退出?还是多个帐户? (3认同)