TransitionKit是iOS中对状态机的一个实现,系统本身是有一个状态机的,不过是在GamePlayKit中的,但是针对平常的业务来说并不实用。
状态机 状态机有四个概念:
state
,状态,一个状态机至少要有2个状态,
event
,事件,事件就是执行某一个指令,触发某一个状态,会引起状态的变换
action
,动作,事件发生之后要执行的动作,
transition
,变换,一个状态变化到另一个状态的抽象过程
TransitionKit也根据以上几个概念,抽象出来4个类:
TKStateMachine
,状态机的抽象类,负责管理状态的变化
TKState
,事件的状态的抽象,在不同时间事物拥有不同的状态
TKEvent
,引起事件状态变化的指令
TKTransition
,状态与状态之间变化的过渡
在学习这个框架之前,先回忆一下初中学习的知识:水有三种状态:气态、液态、固态,一个比较严谨的说法是气态水(水蒸气)、液态水(常说的水)、固态水(冰)。他们之间可以相互转化,转化之间具体的描述为:
TKStateMachine 这个类是整个状态机的抽象,负责管理状态与状态之间的切换,因此状态机中会有多种状态,以及多种引起状态变化的事件。
根据上面对水的三种状态的描述,使用状态机进行抽象就是,一个状态机(水)有三个状态:gas、liquid、solid,六种事件:g2l(液化)、g2s(凝华)、l2s(凝固)、l2g(气化)、s2l(熔化)、s2g(升华)。
三种状态使用该库进行抽象的结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 TKState *gas = [TKState stateWithName:@"gas"]; [gas setDidEnterStateBlock:^(TKState *state, TKTransition *transition) { /// TODO-gas }]; TKState *liquid = [TKState stateWithName:@"liquid"]; [liquid setDidEnterStateBlock:^(TKState *state, TKTransition *transition) { /// TODO-liquid }]; TKState *solid = [TKState stateWithName:@"solid"]; [solid setDidEnterStateBlock:^(TKState *state, TKTransition *transition) { /// TODO-solid }];
而六种事件的抽象如下:
1 2 3 4 5 6 7 8 9 10 11 TKEvent *g2l = [TKEvent eventWithName:@"g2l" transitioningFromStates:@[gas] toState:liquid]; TKEvent *g2s = [TKEvent eventWithName:@"g2l" transitioningFromStates:@[gas] toState:solid]; TKEvent *l2s = [TKEvent eventWithName:@"l2s" transitioningFromStates:@[liquid] toState:solid]; TKEvent *l2g = [TKEvent eventWithName:@"l2g" transitioningFromStates:@[liquid] toState:gas]; TKEvent *s2l = [TKEvent eventWithName:@"s2l" transitioningFromStates:@[solid] toState:liquid]; TKEvent *s2g = [TKEvent eventWithName:@"s2g" transitioningFromStates:@[solid] toState:gas];
有了状态和事件,就可以去设置状态机了:
1 2 3 4 5 6 7 TKStateMachine *stateMachine = [[TKStateMachine alloc] init]; [stateMachine addStates:@[liquid, gas, solid]]; [stateMachine setInitialState:liquid]; [stateMachine addEvents:@[g2l, g2s, l2s, l2g, s2l, s2g]]; [stateMachine activate];
以上就构建起了水的状态机,前两部分创建对应的状态
和事件
都是类的实例化,没有什么可探究的。在第三部分,我们创建了状态机,并且将状态和事件都放入状态机中,先看将所有状态都放入状态机以及设置初始状态中都做了什么。
addState 状态机添加状态的api有两个,一个是添加单个
,一个是添加多个
,不过核心api是添加单个状态。
有一点要先说明一下,状态机本身也有两种状态:活跃
与非活跃
,只有在状态机处于非活跃状态下才可以对其添加state和event ,只有在活跃状态下才可以执行event 。
在添加单个state的时候,会对要确保其是TKState或者其子类,并且没有被添加过
,然后会放入到状态机的状态集合(mutableStates)中:
1 2 3 4 5 6 7 8 9 10 11 - (void)addState:(TKState *)state { TKRaiseIfActive();/// 只能在当前状态机不是活跃的时候才可以添加state // 1.传入的state实例必须要是TKState或者其子类 // 2.state在状态机中没有被添加过 // if (self.initialState == nil) self.initialState = state; [self.mutableStates addObject:state]; }
addEvent 添加事件要复杂有点儿,先看其api:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 - (void)addEvent:(TKEvent *)event { // 1.状态机活跃判断、event非空判断 // 2.如果event有来源state,要确保当前状态机中已经包含这些state if (event.sourceStates) { for (TKState *state in event.sourceStates) { if (! [self.mutableStates containsObject:state]) { // 这里由于状态机的状态集合中没有event的来源state,抛出异常 } } } // 3.确保当前状态机的states数组中包含 目的state // 也就是说event所表示的状态变化只能在当前状态机的state之间 if (! [self.mutableStates containsObject:event.destinationState]) { // 这里由于该event的目的state不包含在状态机的状态集合中,抛出异常 } [self.mutableEvents addObject:event]; }
和添加状态一样,添加事件也需要在状态机处于非活跃状态下进行。由于事件表示的是一个状态变化到另一个状态的原因,所以其来源state
、目的state
都应该在状态机的状态集合中存在
,否则就属于逻辑出错, 会抛出异常。
activate 开始执行状态机,等待对应的事件来临从而改变状态,其内部将一个表示是否活跃的布尔值
进行修改,以及修改当前状态变量(currentState)
,并且使用递归锁
进行锁定,在修改当前状态的时候会调用block将状态传递出去:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // 使状态机变活跃,内部会使用锁来确保数据的变化不混乱 - (void)activate { // 如果状态机已经变活跃了,就不能再调用该方法,属于逻辑错误 if (self.isActive) [NSException raise:NSInternalInconsistencyException format:@"The state machine has already been activated."]; [self.lock lock]; self.active = YES; // 节点事件回调,方便外部处理逻辑 if (self.initialState.willEnterStateBlock) self.initialState.willEnterStateBlock(self.initialState, nil); // 由于状态机是从冷却变为活跃的,所以一定是从初始状态开始的 self.currentState = self.initialState; // 节点事件回调,方便外部处理逻辑 if (self.initialState.didEnterStateBlock) self.initialState.didEnterStateBlock(self.initialState, nil); [self.lock unlock]; }
fireEvent 经过以上步骤,一个状态机就创建好了,并且处于活跃状态
。初始状态机内部的状态改变的原因是状态机收到了某些事件 :
1 [stateMachine fireEvent:l2g userInfo:nil error:nil];
调用这个代码会执行上面的 /// TODO-gas
回调,由于状态机的当前状态为liquid,所以能引起状态发生改变的事件只有:l2s 、l2g 这两个,这个完全符合常识,也完全符合我们这个状态机的规则。
这个api的实现很长,下面会分为三部分来进行讲解,由于这里涉及到对状态机里面集合的get操作,所以会加锁,加锁这部分独立于三部分。
1.获取一个正确的event 由于第一个参数是TKEvent实例或者event的名字(“g2l”、“g2s”、、),所以这里会先通过这个代码获取到合法的event实例对象
:
1 TKEvent *event = [eventOrEventName isKindOfClass:[TKEvent class]] ? eventOrEventName : [self eventNamed:eventOrEventName];
然后要确保这个event的源state存在 ,并且当前状态机的状态存在于event的来源state中 ,如果不存在则说明不能响应这个event来改变状态:
1 2 3 4 5 6 7 8 if (event.sourceStates != nil && ![event.sourceStates containsObject:self.currentState]) { NSString *failureReason;// NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"The event cannot be fired from the current state.", NSLocalizedFailureReasonErrorKey: failureReason }; if (error) *error = [NSError errorWithDomain:TKErrorDomain code:TKInvalidTransitionError userInfo:userInfo]; [self.lock unlock]; return NO; }
2.抽象状态切换 在上一个部分之后,已经获得了一个可以用来改变状态的event,接下来就是要执行状态的修改
了,但是在执行状态的修改之前还有一件事要做。
由于event是引起一系列状态到另一个状态改变的抽象,并不能表示两个具体状态之间的切换 ,因此该库使用TKTransition
这个类来抽象两个状态之间的转换。
在这一部分,会通过上面的参数来创建一个TKTransition
实例对象,然后通过event的一个block回调来决定是否执行该切换:
1 2 3 4 5 6 7 8 9 10 11 12 TKTransition *transition = [TKTransition transitionForEvent:event fromState:self.currentState inStateMachine:self userInfo:userInfo]; if (event.shouldFireEventBlock) { if (! event.shouldFireEventBlock(event, transition)) { // 源代码中这里有一些用于创建error的代码,但是其并没有用,这里忽略 [self.lock unlock]; return NO; } }
3.执行切换状态 在经历了一些列艰难险阻之后,状态机终于可以切换状态了。
状态切换,就是要将状态机的currentState
属性修改为event的destinationState
,因此这里先取出来这两个变量。本来直接将event的destinationState设置给状态机的currentState即可,但是该库提供了很丰富的事件回调,让外部可以有更加细的粒度来处理事件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 TKState *oldState = self.currentState; TKState *newState = event.destinationState; if (event.willFireEventBlock) event.willFireEventBlock(event, transition); if (oldState.willExitStateBlock) oldState.willExitStateBlock(oldState, transition); if (newState.willEnterStateBlock) newState.willEnterStateBlock(newState, transition); self.currentState = newState; NSMutableDictionary *notificationInfo = [userInfo mutableCopy] ?: [NSMutableDictionary dictionary]; // 省略对notiInfo的设置 [[NSNotificationCenter defaultCenter] postNotificationName:TKStateMachineDidChangeStateNotification object:self userInfo:notificationInfo]; if (oldState.didExitStateBlock) oldState.didExitStateBlock(oldState, transition); if (newState.didEnterStateBlock) newState.didEnterStateBlock(newState, transition); if (event.didFireEventBlock) event.didFireEventBlock(event, transition); [self.lock unlock];
至此,状态已经完成了改变。
以上也就是整个状态机框架的实现原理。
参考文章