这里的问题是,你有一个输入信号(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创建另一个中间信号,以使代码更具可读性。
如果pincodeEqual
是YES
(这是enter4digit
上的第二个和第三个值相等时的情况)那么我们返回值,否则我们返回立即发送错误的信号。在这里,replyLast
在secondInput
和thirdInput
是重要的,因为这个值是事件发送后几次需要的!
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连接到流程。
通过将问题建模为状态机,可以大大简化(在我看来)。
所以,你将有一个状态,并基于该状态和输入状态的变化。
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
这些结合在一起,通过启动input
与nil
,previousInput
总是在后面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
并相应地更新您的用户界面,可以更轻松地更新您的用户界面。显示错误消息或提供重新开始(实质上通过将状态机重置为初始状态)。特别是后者在第一种解决方案中要难得多,因为在那里,我们在输入信号上跳过明确的特定数字(然后甚至终止)...
这两种方式都很出色,特别是状态机。 谢谢师父。 我在哪里可以学习RX技术,如状态机? –
以这种方式使用状态机的想法源自[redux](http://redux.js.org)。 – MeXx