在泊学 App 里,我们要处理很多状态。例如,之前创建首页 model 的时候我们就提到过,要根据用户未登录,已登录和订阅已过期做不同的响应,这就是三种不同的用户状态。另外,由于我们还会缓存从服务器获取的数据,因此,已缓存和未缓存又是两种不同的状态。单就把我们说过的这些情况组合起来,就会有 6 种不同的情况,并且,这些状态还有可能发生变化,我们还要针对这些变化做出响应。
面对这种需求,显然组合多个 if ... else ...
语句听上去并不是太好的办法,因为无论是用户登录状态,还是数据的缓存情况,这些都不是某一个界面需要的数据。即便你有足够的耐心和经验正确地写出复杂的 if
判断,让你在处理每个界面的时候,都写上一波这样的代码估计你就受不了了。
一个更好的办法是,我们把相关的状态,以及这些状态的变化进行一次抽象,用一个独立的类型来管理它们。这种类型,我们管它叫做状态机。虽然听名字挺复杂的,但它要完成的工作很简单。通常,一个状态机要处理三个问题:
- 拥有那些状态;
- 这些状态的转换规则如何;
- 以及当发生状态变化时如何通知外界;
StateMachine
于是,围绕着这三个问题,在 BoxueDataKit / Extensions / StateMachine.swift 里,我们定义了一个简单的状态机 StateMachine
:
class StateMachine<P: StateMachineDelegate> {
}
实际上,这个类型自身并没有处理任何关于状态以及状态变化的细节,它只对这套机制应该如何工作定义了一个框架。
而具体的内容,则是它的泛型参数 P
,也就是 StateMachineDelegate
完成的:
protocol StateMachineDelegate: class {
associatedtype StateType: StateMachineDataSource
func didTransitionFrom(_ from: StateType, to: StateType)
}
其中,didTransitionFrom
用于定义当状态机管理的状态从 from
变成 to
时执行的操作。通过它,我们可以处理上面提出的第三个问题。而 StateType
则是状态机管理的状态,它是一个实现了 StateMachineDataSource
的类型:
protocol StateMachineDataSource {
func shouldTransitionFrom(_ from: Self, to: Self) -> Bool
}
这个协议,约束了一个 shouldTransitionFrom
方法,它定义了状态是否可以从 from
变成 to
,也就是我们刚才提出的第二个问题。至此,一个状态机要处理的三个问题,就都有着落了:
- 拥有那些状态:
StateType
; - 这些状态的转换规则如何:
shouldTransitionFrom
; - 以及当发生状态变化时如何通知外界:
didTransitionFrom
;
我们来看 StateMachine
自身的实现。首先,它有一个私有属性,用来保存状态机当前的状态:
class StateMachine<P: StateMachineDelegate> {
private var _state: P.StateType {
didSet {
delegate.didTransitionFrom(oldValue, to: _state)
}
}
}
可以看到,给它赋值之后,我们通过 delegate
调用了 didTransitionFrom
方法。之所以,要把 _state
定义成 private
,是因为状态机状态的改变,并不是随意的,而是有条件的。但是,Swift 当前并不支持所谓“条件化赋值”这样的操作,为此,我们得把 _state
包上一层赋值判断之后再公开出去:
/// Inside `StateMachine`
var state: P.StateType {
get {
return _state
}
set {
if _state.shouldTransitionFrom(_state, to: newValue) {
_state = newValue
}
}
}
在 set
的部分,我们先使用 shouldTransitionFrom
判读了 _state
是否可以从当前值变成 newValue
,如果允许,才会赋值,进而触发 didTransitionFrom
方法。否则,就什么都不发生。
最后,让 StateMachine
包含一个 P
类型的对象,把状态管理的工作真正交出去:
class StateMachine<P: StateMachineDelegate> {
/// The same as before...
var delegate: P
init(initialState: P.StateType, delegate: P) {
/// The initial state isn't technically a "transition".
/// So we set `_state` directly.
self._state = initialState
self.delegate = delegate
}
}
在 init
的实现里,唯一值得说下的,是对 _state
的赋值,严格来说,对 _state
对初始化并不算是状态的改变,因此,我们直接赋值了 _state
。
What's next?
至此,这个简单的状态机就完成了。下一节,我们用泊学 App 中的两个例子:一个管理登录状态,一个管理从网络加载数据的状态来看看 StateMahcine
的具体应用。