使用ReactiveCocoa重试asynchronous操作

我正在使用ReactiveCocoa信号来表示对我们系统中RESTful后端的调用。 每个RESTful调用都应该接收一个令牌作为参数之一。 令牌本身是从authenticationAPI调用接收的。

所有工作正常,我们现在引入令牌过期,所以后端访问类可能需要重新授权本身,如果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; } 

这是正确的做法吗?

首先,应尽可能避免订阅和主题 。 嵌套订阅尤其是反模式 – 通常有信号操作员可以取代它们。

在这种情况下,我们需要利用信号可以表示延迟工作的事实,并且只创build一个信号来执行实际的请求:

 // This was originally the `first` signal. RACSignal *apiCall = [RACSignal defer:^{ return [self apiCall:base params:p get:get]; }]; 

使用+defer:在这里确保没有工作将开始,直到订阅 。 一个重要的推论是,这项工作可以通过多次订阅来重复

例如,如果我们发现错误,我们可以尝试获取一个标记,然后返回相同的延迟信号来表明它应该再次尝试:

 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]; 

使用-replayreplace之前存在的RACReplaySubject ,并立即开始请求; 然而,它也可能是-replayLazily ,甚至完全消除(每次订购重做一次呼叫)。

而已! 需要指出的是,不需要明确的订阅来build立将要执行的工作。 订阅通常只发生在程序的“离开”处 – 调用者实际请求执行工作的地方。