2017-08-28 26 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