如何在进行API调用之前使用ReactiveCocoa进行透明身份验证? [英] How to using ReactiveCocoa to transparently authenticate before making API calls?
问题描述
我在一个应用程序中使用ReactiveCocoa调用远程Web API。但在从给定API主机检索任何内容之前,应用程序必须提供用户的凭据并检索API令牌,然后用于对后续请求进行签名。
I am using ReactiveCocoa in an app which makes calls to remote web APIs. But before any thing can be retrieved from a given API host, the app must provide the user's credentials and retrieve an API token, which is then used to sign subsequent requests.
我想抽象掉这个身份验证过程,以便每当我进行API调用时自动发生。假设我有一个包含用户凭据的API客户端类。
I want to abstract away this authentication process so that it happens automatically whenever I make an API call. Assume I have an API client class that contains the user's credentials.
// 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];
如何使用ReactiveCocoa透明地导致对API的第一个(而且只有第一个)请求检索和作为副作用,在任何后续请求之前安全地存储API令牌?
How can I use ReactiveCocoa to transparently cause the first (and only the first) request to an API to retrieve and, as a side effect, safely store an API token before any subsequent requests are made?
这也是一个要求,我可以使用 combineLatest:
(或类似)启动多个并发请求,
It is also a requirement that I can use combineLatest:
(or similar) to kick off multiple simultaneous requests and that they will all implicitly wait for the token to be retrieved.
RAC(self.tupleOfThisAndThat) = [RACSignal combineLatest:@[ [apiClient getThis], [apiClient getThat]]];
此外,如果检索令牌请求已经在调用API调用时,
Further, if the retrieve-token request is already in flight when an API call is made, that API call must wait until the retrieve-token request has completed.
我的部分解决方案如下:
My partial solution follows:
将使用 flattenMap:
将产生令牌的信号映射到给定令牌,执行所需请求并产生API调用的结果的信号。
The basic pattern is going to be to use flattenMap:
to map a signal which yields the token to a signal that, given the token, performs the desired request and yields the result of the API call.
假设对 NSURLRequest
有一些方便的扩展:
Assuming some convenient extensions to 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];
}
}
现在考虑 -getToken
。
- 在琐碎的情况下,当已经检索到令牌时,
- 如果没有检索到令牌,则订阅会延迟到返回令牌的身份验证API调用。
- 如果身份验证API调用正在进行中,应该可以安全地添加另一个观察者,而不会通过线路重复身份验证API调用。
我不知道如何做到这一点。此外,如何和在哪里安全地存储令牌?某些类型的持续/可重复信号?
However I'm not sure how to do this. Also, how and where to safely store the token? Some kind of persistent/repeatable signal?
推荐答案
因此,这里有两个主要的事情:
So, there are two major things going on here:
- 您希望在每次有新订阅者时共享一些副作用(在这种情况下,获取令牌),而不重新触发。
- 您希望任何订阅
-getToken
的用户都能获得相同的值。
- You want to share some side effects (in this case, fetching a token) without re-triggering them every time there's a new subscriber.
- You want anyone subscribing to
-getToken
to get the same values no matter what.
为了共享副作用(上面#1),我们将使用 RACMulticastConnection 。像文档所说:
In order to share side effects (#1 above), we'll use RACMulticastConnection. Like the documentation says:
多播连接封装了向许多订户共享一个订阅信号的想法。如果订阅底层信号涉及副作用,或者不应该多次调用,那么这是最常见的。
A multicast connection encapsulates the idea of sharing one subscription to a signal to many subscribers. This is most often needed if the subscription to the underlying signal involves side-effects or shouldn't be called more than once.
添加其中一个作为API客户端类的私有属性:
Let's add one of those as a private property on the API client class:
@interface APIClient ()
@property (nonatomic, strong, readonly) RACMulticastConnection *tokenConnection;
@end
现在,这将解决N个当前订阅者都需要相同的未来结果(API调用正在等待请求令牌正在传输),但是我们仍然需要其他东西,以确保未来订阅者获得相同的结果(已经获取的令牌),无论何时他们订阅。
Now, this will solve the case of N current subscribers that all need the same future result (API calls waiting on the request token being in-flight), but we still need something else to ensure that future subscribers get the same result (the already-fetched token), no matter when they subscribe.
这是 RACReplaySubject 用于:
重放主题保存发送的值(达到其定义的容量),并重新发送给新订阅者。
A replay subject saves the values it is sent (up to its defined capacity) and resends those to new subscribers. It will also replay an error or completion.
为了将这两个概念绑定在一起,我们可以使用RACSignal's-multicast:method ,它将正常信号转换为连接
To tie these two concepts together, we can use RACSignal's -multicast: method, which turns a normal signal into a connection by using a specific kind of subject.
我们可以在初始化时连接大部分行为:
We can hook up most of the behaviors at initialization time:
- (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;
}
然后,我们实现 -getToken
可以延迟触发抓取:
Then, we implement -getToken
to trigger the fetch lazily:
- (RACSignal *)getToken {
// Performs the actual fetch if it hasn't started yet.
[self.tokenConnection connect];
return self.tokenConnection.signal;
}
之后,任何订阅 getToken
(如 -requestSignalWithURLRequest:
)将获取令牌,如果它还没有被提取,开始提取它,如果必要,或等待飞行中的请求(如果有)。
Afterwards, anything that subscribes to the result of -getToken
(like -requestSignalWithURLRequest:
) will get the token if it hasn't been fetched yet, start fetching it if necessary, or wait for an in-flight request if there is one.
这篇关于如何在进行API调用之前使用ReactiveCocoa进行透明身份验证?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!