我想抽象掉这个身份验证过程,以便每当我进行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];
如何使用ReactiveCocoa透明地导致对API的第一个(且仅第一个)请求检索,并作为副作用,在任何后续请求之前安全地存储API令牌?
这也是一个要求,我可以使用combineLatest :(或类似)启动多个并发请求,他们将隐式等待令牌被检索。
RAC(self.tupleOfThisAndThat) = [RACSignal combineLatest:@[ [apiClient getThis],[apiClient getThat]]];
此外,如果在进行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]; } }
现在考虑-getToken的订阅实现。
>在琐碎的情况下,当令牌已经被检索时,订阅立即产生令牌。
>如果尚未检索到令牌,则订阅会延迟到返回令牌的身份验证API调用。
>如果认证API调用正在进行,则应该安全地添加另一个观察者,而不会导致通过线重复认证API调用。
但我不知道如何做到这一点。此外,如何和在哪里安全地存储令牌?某种持续/可重复信号?
>你想共享一些副作用(在这种情况下,获取令牌),而不是每次有新的订阅者重新触发。
>您希望任何订阅-getToken的人获得相同的值,无论什么。
为了共享副作用(上面#1),我们将使用RACMulticastConnection.像文档说:
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.
@interface APIClient () @property (nonatomic,strong,readonly) RACMulticastConnection *tokenConnection; @end
现在,这将解决所有需要相同的未来结果(API调用在请求令牌正在进行中)的N个当前订阅者的情况,但是我们仍然需要别的以确保未来的订阅者获得相同的结果(已经 – 获取令牌),无论他们什么时候订阅。
这是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,它通过使用特定类型的主题将正常信号转换为连接。
我们可以在初始化时连接大多数行为:
- (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懒惰地触发抓取:
- (RACSignal *)getToken { // Performs the actual fetch if it hasn't started yet. [self.tokenConnection connect]; return self.tokenConnection.signal; }
之后,订阅-getToken(如-requestSignalWithURLRequest :)的结果的任何内容都将获取令牌(如果尚未提取),如果需要,请开始提取令牌,或者等待正在传送的请求(如果有)。