2017-08-28 65 views
0

我正在實現簡單的PincodeViewController。它看起來像: 控制ReactiveCocoa中ViewController的狀態

(剛剛從谷歌一個隨機的形象,但我的是一樣的)

它有3個步驟:輸入當前PIN碼 - >輸入新的PIN代碼 - >確認。

目前,我在3元信號,像

RACSignal *enter4digit = ... // input 4 digits 
RACSignal *passcodeCorrect = ... // compare with stored pincode 
RACSignal *pincodeEqual = ... // confirm pincode in step 3 

,並綁定在一起

RACSignal *step1 = [RACSignal combineLatest:@[enter4digit, passcodeCorrect]]; 
RACSignal *step2 = [RACSignal combineLatest:@[enter4digit, stage1]]; 
RACSignal *step3 = [RACSignal combineLatest:@[enter4digit, pincodeEqual, stage2]]; 

它不工作。我該如何處理它?

任何意見,將不勝感激。謝謝。

回答

1

這裏的問題是,你有一個輸入信號(4位數字輸入),並根據什麼發送什麼信號(以及當前的初始pincode),應該發生不同的事情。

複雜的解決方案

您可以通過分解到這一點不同的信號,你開始,在這種情況下,你需要挑選出第一,第二和第三值的pincodeInput信號,並做不同的事情處理這那些,例如要創建pincodeCorrect信號,您應該:

RACSignal *firstInput = [enter4digit take:1]; 
RACSignal *pincodeCorrect = [[[RACSignal return:@(1234)] combineLatestWith:firstInput] map:^NSNumber *(RACTuple *tuple) { 
    RACTupleUnpack(NSNumber *storedPincode, NSNumber *enteredPincode) = tuple; 
    return @([storedPincode isEqualToNumber:enteredPincode]); 
}]; 

其中1234是當前PIN碼。

而對於pincodeEqual你需要的enter4digit第二和第三個值:

RACSignal *secondInput = [[[enter4digit skip:1] take:1] replayLast]; 
RACSignal *thirdInput = [[[enter4digit skip:2] take:1] replayLast]; 

RACSignal *pincodeEqual = [[secondInput combineLatestWith:thirdInput] map:^NSNumber *(RACTuple *tuple) { 
    RACTupleUnpack(NSNumber *pincode, NSNumber *pincodeConfirmation) = tuple; 
    return @([pincode isEqualToNumber:pincodeConfirmation]); 
}]; 

綁定在一起變得更爲複雜一點,但。它可以通過if:then:else運營商完成。 我正在爲新的pincode創建另一箇中間信號,以使代碼更具可讀性。

如果pincodeEqualYES(這是enter4digit上的第二個和第三個值相等時的情況)那麼我們返回值,否則我們返回立即發送錯誤的信號。在這裏,replyLastsecondInputthirdInput是重要的,因爲這個值是事件發送後幾次需要的!

NSError *confirmationError = [NSError errorWithDomain:@"" code:0 userInfo:@{NSLocalizedDescriptionKey: @"Confirmation Wrong"}]; 
RACSignal *changePincode = [RACSignal 
          if:pincodeEqual 
          then:thirdInput 
          else:[RACSignal error:confirmationError]]; 

爲了得到實際的整個過程中,我們幾乎再次做同樣:

NSError *pincodeError = [NSError errorWithDomain:@"" code:1 userInfo:@{NSLocalizedDescriptionKey: @"Pincode Wrong"}]; 
RACSignal *newPincode = [RACSignal 
         if:pincodeCorrect 
         then:changePincode 
         else:[RACSignal error:pincodeError]]; 

信號newPincode將現在要麼發送新的PIN碼的值後的整個過程是成功的,或錯誤(可能是當前的PIN碼是錯誤的,或者說確認是錯誤的)

使用狀態機

這一切據說,我認爲上面的解決方案非常複雜,容易犯錯誤,很難將其餘的UI連接到流程。

通過將問題建模爲狀態機,可以大大簡化(在我看來)。

FSM

所以,你將有一個狀態,並基於該狀態和輸入狀態的變化。

typedef NS_ENUM(NSInteger, MCViewControllerState) { 
    MCViewControllerStateInitial, 
    MCViewControllerStateCurrentPincodeCorrect, 
    MCViewControllerStateCurrentPincodeWrong, 
    MCViewControllerStateNewPincodeEntered, 
    MCViewControllerStateNewPincodeConfirmed, 
    MCViewControllerStateNewPincodeWrong 
}; 

嗯,其實,狀態變化基礎上,當前的狀態,當前輸入以前的輸入。因此,讓我們創建信號,所有的這些:

RACSignal *state = RACObserve(self, state); 
RACSignal *input = enter4digit; 
RACSignal *previousInput = [input startWith:nil]; 

我們將在後面zip這些結合在一起,通過啓動inputnilpreviousInput總是在後面input正好值1,因此,當一個新的值到達上input,在以前價值將同時發送到previousInput。現在

,我們需要這三個組合在一起來創建新的狀態:

RAC(self, state) = [[RACSignal zip:@[state, input, previousInput]] map:^NSNumber *(RACTuple * tuple) { 
    RACTupleUnpack(NSNumber *currentStateNumber, NSNumber *pincodeInput, NSNumber *previousInput) = tuple; 
    MCViewControllerState currentState = [currentStateNumber integerValue]; 

    // Determine the new state based on the current state and the new input 
    MCViewControllerState nextState; 

    switch (currentState) { 
     case MCViewControllerStateInitial: 
      if ([pincodeInput isEqualToNumber:storedPincode]) { 
       nextState = MCViewControllerStateCurrentPincodeCorrect; 
      } else { 
       nextState = MCViewControllerStateCurrentPincodeWrong; 
      } 
      break; 
     case MCViewControllerStateCurrentPincodeCorrect: 
      nextState = MCViewControllerStateNewPincodeEntered; 
      break; 
     case MCViewControllerStateNewPincodeEntered: 
      if ([pincodeInput isEqualToNumber:previousInput]) { 
       nextState = MCViewControllerStateNewPincodeConfirmed; 
      } else { 
       nextState = MCViewControllerStateNewPincodeWrong; 
      } 
      break; 

     default: 
      nextState = currentState; 
    } 

    return @(nextState); 
}]; 

使用zip,我們將計算與map正好一個新的狀態,一旦的到達每一個新的價值在input。 內部map WEG總是獲取當前狀態,當前輸入和之前的輸入,現在可以基於這三個值計算下一個狀態。

現在,通過再次觀察self.state並相應地更新您的用戶界面,可以更輕鬆地更新您的用戶界面。顯示錯誤消息或提供重新開始(實質上通過將狀態機重置爲初始狀態)。特別是後者在第一種解決方案中要難得多,因爲在那裏,我們在輸入信號上跳過明確的特定數字(然後甚至終止)...

+0

這兩種方式都很出色,特別是狀態機。 謝謝師父。 我在哪裏可以學習RX技術,如狀態機? –

+1

以這種方式使用狀態機的想法源自[redux](http://redux.js.org)。 – MeXx