2014-01-20 46 views
7

我使用ReactiveCocoa信号来表示对系统中RESTful后端的调用。每个RESTful调用都应该接收一个令牌作为参数之一。令牌本身是从认证API调用接收的。使用ReactiveCocoa重试异步操作

所有的工作都很好,现在我们引入了令牌过期,所以如果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; 
} 

这是正确的方法吗?

回答

12

首先,和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甚至完全取消(每次订购重做一次呼叫)。

就是这样!重要的是要指出,没有明确的订阅只需要设置将执行的工作。订阅通常只发生在程序的“离开”处 - 调用者实际请求执行工作的地方。

+0

感谢您的回答!除了'p'变量没有更新的事实,我看起来都很好。在我的代码片段中,调用'[self apiCall:params:get:]'两次调用*不同的*值'p'(第二次调用发生在更新后的令牌中)。在你的代码片段中,这可能会也可能不是这样(取决于代码的不同部分是否共享对象引用)。什么是使参数更新显式的好方法? –

+0

'p'在上面的示例中以相同的方式更新。只要它是一个可变的字典,块就会看到更新。如果您想更明确一些,可以将'apiCall'变成一个代码块,其中包含''p'字典,然后_returns_用于API调用的'RACSignal'。 –

+0

如果你想尝试获取令牌固定次数或重复直到它是正确的?我正在考虑你从用户那里获得凭据的情况,因此想要让用户有一两次机会来纠正或停止前进,直到他们做对了。 – Ayal