我正在使用ReactiveCocoa信号来表示对我们系统中RESTful后端的调用。每个RESTful调用都应该接收一个令牌作为参数之一。令牌本身是从身份验证API调用接收的。
一切正常,我们现在引入了令牌过期,因此如果API调用因HTTP代码403失败,后端访问类可能需要重新授权。我想让这个操作对调用者完全透明,这就是最好的我想出来了:
- (RACSignal *)apiCallWithSession:(Session *)session base:(NSString *)base params:(NSDictionary *)params get:(BOOL)get {
NSMutableDictionary* p = [params mutableCopy];
p[@"token"] = session.token;
RACSubject *subject = [RACReplaySubject subject];
RACSignal *first = [self apiCall:base params:p get:get]; // this returns the signal representing the asynchronous HTTP operation
@weakify(self);
[first subscribeNext:^(id x) {
[subject sendNext:x]; // if it works, all is fine
} error:^(NSError *error) {
@strongify(self);
// if it doesn't work, try re-requesting a token
RACSignal *f = [[self action:@"logon" email:session.user.email password:session.user.password]
flattenMap:^RACStream *(NSDictionary *json) { // and map it to the other instance of the original signal to proceed with new token
NSString *token = json[@"token"];
p[@"token"] = token;
session.token = token;
return [self apiCall:base params:p get:get];
}];
// all signal updates are forwarded, we're only re-requesting token once
[f subscribeNext:^(id x) {
[subject sendNext:x];
} error:^(NSError *error) {
[subject sendError:error];
} completed:^{
[subject sendCompleted];
}];
} completed:^{
[subject sendCompleted];
}];
return subject;
}
这是正确的方法吗?
答案 0 :(得分:12)
首先,通常应尽可能避免使用subscriptions和subjects。特别是嵌套订阅是一种反模式 - 通常有信号运算符可以替代它们。
在这种情况下,我们需要利用信号可以表示延迟工作的事实,并且只创建一个信号来执行实际请求:
// This was originally the `first` signal.
RACSignal *apiCall = [RACSignal defer:^{
return [self apiCall:base params:p get:get];
}];
在此使用+defer:
可确保no work will begin until subscription。一个重要的推论是,通过多次订阅可以重复。
例如,如果我们发现错误,我们可以尝试获取令牌,然后返回相同的延迟信号以指示应该再次尝试:
return [[apiCall
catch:^(NSError *error) {
// If an error occurs, try requesting a token.
return [[self
action:@"logon" email:session.user.email password:session.user.password]
flattenMap:^(NSDictionary *json) {
NSString *token = json[@"token"];
p[@"token"] = token;
session.token = token;
// Now that we have a token, try the original API call again.
return apiCall;
}];
}]
replay];
使用-replay
替换之前的RACReplaySubject
,并立即启动请求;但是,它也可以-replayLazily
甚至完全消除(每次订阅重拨一次)。
就是这样!重要的是要指出,仅仅设置将要执行的工作不需要显式订阅。订阅通常应该只发生在程序的“离开”处 - 调用者实际上要求执行工作。