XE2V 项目收获系列:
一、YLExtensions:让 UITableView 及 UICollectionView 更易用
前言
为了实现自动刷新,我创建了一个状态机模型。此状态机包含 StateType.swift、ActionType.swift、OperatorType.swift 和 StateMachine.swift 四个文件,我会逐一进行介绍。
StateType
状态机自然是有状态的,但状态是什么?如何定义它?考察一个具体的实例会很有帮助。让我们拿刷新来看,首先,它在开始时已经有了一个状态,可以称为初始状态;其次,状态看起来有两种情况,一种是稳定的,如“已完成刷新”的状态,另一种是过渡性的状态,如“刷新中”这个状态。据此,我们给出状态的定义:
/// 状态的稳定性:稳定的 or 过渡的
enum Stability {
case stable
case transitional
}
protocol StateType {
var stability: Stability { get }
static var initialState: Self { get }
}
ActionType
状态是会改变的,什么促成了状态的改变?动作。动作又是什么?它是使得状态发生改变的 something。状态改变过程是怎么样的?它会由一个稳定状态 A,经由过渡状态 B,转化为另一个稳定状态 C。于是,我们可以用那个过渡状态来描述动作:
protocol ActionType {
associatedtype State: StateType
var transitionalState: State { get }
}
OperatorType
有了状态和动作,还缺什么?考虑一下,动作是如何让状态发生改变的?自然是动作触发了操作,操作令状态发生变化。我们需要某样东西承载操作,于是,便有了 OperatorType
。这个类型应该有些什么呢?嗯,它应该有一个函数,在它里面进行操作改变状态。那,这个函数长什么样呢?函数会改变状态,而改变状态的是动作,所以必定有一个动作属性;它还应该提供一个接口,方便状态改变之后进行后续处理。最后,这个类型应该还可以方便的在状态改变前后进行一些额外的操作。综上,OperatorType
可以这样定义:
protocol OperatorType {
associatedtype Action: ActionType
/// 开始过渡前调用
func startTransition(_ state: Action.State)
/// 过渡时调用
func transition(with action: Action, completion: @escaping (Action.State) -> Void)
/// 结束过渡后调用
func endTransition(_ state: Action.State)
}
extension OperatorType {
func startTransition(_ state: Action.State) { }
func endTransition(_ state: Action.State) { }
}
StateMachine
终于可以开始构建状态机了。状态机是如何进行工作的呢?动作发生时,它通过我们提供的操作改变状态
。所以,需要给状态机传入一个 operator。它应该有一个方法,当我们传入一个动作时,它在内部调用 operator 改变状态。当然,它还需要一个属性来记录当前的状态。有了上面的讨论,这里给出状态机的实现:
class StateMachine<Operator: OperatorType>: NSObject {
var completionHandler: (() -> Void)?
private(set) var `operator`: Operator
private(set) var currentState: Operator.Action.State = .initialState {
didSet {
switch currentState.stability {
case .transitional:
`operator`.startTransition(currentState)
case .stable:
`operator`.endTransition(oldValue)
completionHandler?()
}
}
}
init(operator: Operator) {
self.`operator` = `operator`
}
func trigger(_ action: Operator.Action, completion: (() -> Void)? = nil) {
guard currentState.stability != .transitional else { return }
currentState = action.transitionState
`operator`.transition(with: action) { (state) in
self.currentState = state
completion?()
}
}
}
使用
首先,定义一系列状态:
enum RefreshState: StateType {
case initial
case refreshing
case paginated
...
var stability: Stability {
switch self {
case .refreshing:
return .transitional
default:
return .stable
}
}
static var initialState: RefreshState {
return .initial
}
}
然后,定义一系列动作:
enum RefreshAction: ActionType {
case pullToRefresh
case loadingMore
var transitionState: RefreshState {
switch self {
...
}
}
}
之后,定义 Operator:
class RefreshOperator: OperatorType {
typealias Action = RefreshAction
...
func transition(with action: Action, completion: @escaping (Action.State) -> Void) {
...
}
}
接着,创建状态机:
let refreshOperator = RefreshOperator()
let refreshStateMachine = StateMachine(operator: refreshOperator)
最后,进行使用:
refreshStateMachine.trigger(someAction) {
...
}
使用实例请参考我的另一个库: YLRefreshKit。
下篇预告
在写项目的过程中,因为经验不足,ViewModel 页面很快变得冗长无比。我决定对 ViewModel 进行精简,但从哪里下手呢?我注意到每个 ViewModel 页面都有一个刷新操作,便尝试把这些刷新操作统一在一起处理。很幸运,我的尝试取得了成功。下篇文章中,我会介绍这个尝试的成果──YLRefreshKit。
源码地址:YLStateMachine