在泊学 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 的具体应用。

所有订阅均支持 12 期免息分期

¥ 59

按月订阅

一个月,观看并下载所有视频内容。初来泊学,这可能是个最好的开始。

开始订阅

¥ 512

按年订阅

一年的时间,让我们一起疯狂地狩猎知识吧。比按月订阅优惠 28%

开始订阅

¥ 1280

泊学终身会员

永久观看和下载所有泊学网站视频,并赠送 100 元商店优惠券。

我要加入
如需帮助,欢迎通过以下方式联系我们