【XE2V 项目收获系列】二、YLStateMachine:一个简单的状态机

1,255 阅读3分钟

XE2V 项目收获系列:

一、YLExtensions:让 UITableView 及 UICollectionView 更易用

二、YLStateMachine:一个简单的状态机

三、YLRefreshKit:有了它,你可以删除你的刷新代码了

四、Decomposer:一个面向协议的构架模式

五、面向协议编程与操作自动化──以实现刷新自动化为例

前言

为了实现自动刷新,我创建了一个状态机模型。此状态机包含 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