2014-01-20 53 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