XE2V Project Harvest series:

Tensions: To make the UITableView and UICollectionView more usable

YLStateMachine: a simple state machine

YLRefreshKit: With it, you can delete your refresh code

Decomposer: a protocol-oriented architecture pattern

Protocol oriented programming and operation automation ── taking refresh automation as an example

preface

To implement the automatic refresh, I created a state machine model. Swift, ActionType. Swift, OperatorType. Swift, and StateMachine.

StateType

A state machine is naturally stateful, but what is the state? How do you define it? It would be helpful to look at a specific example. Let’s take the refresh. First of all, it already has a state at the beginning, which can be called the initial state. Second, states appear to come in two forms: a stable state, such as the “flush completed” state, and a transitional state, such as the “flush in progress” state. Accordingly, we give the definition of state:

/// State stability: stable or transitive
enum Stability {
    case stable
    case transitional
}

protocol StateType {
    var stability: Stability { get }
    static var initialState: Self { get}}Copy the code

ActionType

States can change. What causes states to change? The action. What’s the action? It’s something that changes the state. What is the state change process? It’s going to go from A stable state, A, through A transition state, B, to another stable state, C. So, we can use that transition state to describe the action:

protocol ActionType {
    associatedtype State: StateType
    var transitionalState: State { get}}Copy the code

OperatorType

With form and action, what’s missing? Think about it, how does the action change the state? Of course, the action triggers the action, and the action causes the state to change. We need something to host the operation, so we have the OperatorType. What should this type have? Well, it should have a function in it that does something to change the state. So, what does this function look like? Functions change state, and actions change state, so there must be an action property; It should also provide an interface for subsequent processing after a state change. Finally, this type should make it easy to do some additional operations before and after state changes. To sum up, OperatorType can be defined like this:

protocol OperatorType {
    associatedtype Action: ActionType
    
    /// called before the transition begins
    func startTransition(_ state: Action.State)
    /// called when transitioning
    func transition(with action: Action.completion: @escaping (Action.State) - >Void)
    // call after the transition ends
    func endTransition(_ state: Action.State)
}

extension OperatorType {
    func startTransition(_ state: Action.State){}func endTransition(_ state: Action.State){}}Copy the code

StateMachine

You’re finally ready to start building your state machine. How does the state machine work? As the action occurs, it changes state through the actions we provide. Therefore, you need to pass an operator to the state machine. It should have a method that internally calls operator to change the state when we pass an action. Of course, it also needs a property to record the current state. With the above discussion, here is the implementation of the state machine:

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?()}}}Copy the code

use

First, define a set of states:

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
    }
}
Copy the code

Then, define a sequence of actions:

enum RefreshAction: ActionType {
    case pullToRefresh
    case loadingMore
    
    var transitionState: RefreshState {
        switch self {
        .}}}Copy the code

After that, define Operator:

class RefreshOperator: OperatorType {
    typealias Action = RefreshAction
    .
    func transition(with action: Action.completion: @escaping (Action.State) - >Void) {
        .}}Copy the code

Next, create the state machine:

let refreshOperator = RefreshOperator(a)let refreshStateMachine = StateMachine(operator: refreshOperator)
Copy the code

Finally, use:

refreshStateMachine.trigger(someAction) {
    .
}
Copy the code

For example, please refer to my other library: YLRefreshKit.

The next trailer

In the process of writing a project, the ViewModel page quickly became tedious due to inexperience. I decided to simplify the ViewModel, but where to start? I noticed that each ViewModel page had a refresh operation and tried to keep them all together. Fortunately, my attempt was successful. In the next article, I will introduce the result of this attempt, YLRefreshKit.

Source code address: YLStateMachine