seancatkinson / SCAStateMachine

A lightweight state machine built in Swift

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

SCAStateMachine

Carthage compatible Docs 100%

A lightweight state machine built in Swift for iOS & Mac OSX. For swift 3 see the swift3 branch.

Features

  • Simple, readable API
  • Flexible - States can be of any type conforming to the Hashable protocol
  • Supports an arbitrary number of states and state changes
  • Block-based API
  • Several action points to customise when various blocks are executed
  • Pass arbitrary data to your state changes
  • Add 'gates' for more advanced customisation of allowed state changes
  • Basic Usage support to get going with minmal setup
  • Advanced Usage support to control which states can be changed to which states
  • Uses Swift 2.0 error mechanism for communicating issues
  • Lightweight - SCAStateMachine has no dependencies beyond Foundation
  • All methods documented and unit tested
  • Supports iOS, macOS, tvOS and watchOS

Example Usage

Classic Turnstile example

import SCAStateMachine

enum TurnstileState {
    case Locked
    case Unlocked
}

let stateMachine = StateMachine(initialState: TurnstileState.Locked)

stateMachine.performAfterChangingTo([.Locked]) { _,_,_ in lock() }
stateMachine.performAfterChangingTo([.Unlocked]) { _,_,_ in unlock() }

stateMachine.addStateTransition(named: "Coin", from: [.Locked], to: .Unlocked)
stateMachine.addStateTransition(named: "Push", from: [.Unlocked], to: .Locked)

do {
    let destinationState = try stateMachine.canPerformTransition(named:"Coin") // returns unlocked
    // do something with the destination state
}
catch {
    // catch UnspportedStateChange/NoTransitionMatchingName/Custom Errors
}

do {
    try stateMachine.performTransition(named:"Coin")
    print(stateMachine.currentState) // Unlocked
    try stateMachine.performTransition(named: "Push")
    print(stateMachine.currentState) // Locked
}
catch {
    // catch UnspportedStateChange/NoTransitionMatchingName/Custom Errors
}

Manual Usage -

import SCAStateMachine

enum LoadingState {
    case Ready
    case Loading
    case Loaded
    case Error
}

enum MyCustomError : ErrorType {
    case CustomErrorOne
}

func mySuccessCheck() -> Bool {
    return true
}

let stateMachine = StateMachine(initialState: LoadingState.Ready)

// ready, loaded and error states can all move to .Loading
stateMachine.allowChangingTo(.Loading, from: [.Ready, .Loaded, .Error])

// .Loading states can move to both .Loaded and .Error states
stateMachine.allowChangingFrom(.Loading, to: [.Loaded, .Error])

// GATES: - Run a custom closure before a change is attempted to check if it should be allowed to go ahead
// Throw custom errors from these closures and they will be picked up later :)
stateMachine.checkConditionBeforeChangingTo([.Loaded]) { (destinationState, startingState, userInfo) -> () in
    if mySuccessCheck() == false {
        throw MyCustomError.CustomErrorOne
    }
}

// do something after changing to the .Error or .Loaded states
stateMachine.performAfterChangingTo([.Error, .Loaded]) { (destinationState, startingState, userInfo) -> () in
    print("We just moved to either .Error or .Loaded")
}

// do something after changing from the .loaded or .Error states
stateMachine.performAfterChangingFrom([.Error, .Loading]) { (destinationState, startingState, userInfo) -> () in
    print("We just moved from .Error or .Loading")
}

// do something after changing from any state
stateMachine.performAfterChanging { (destinationState, startingState, userInfo) -> () in
    print("I get performed after any and every change")
}


// check you can change before changing
do {
    try stateMachine.canChangeTo(.Loaded)
}
catch MyCustomError.CustomErrorOne {
    // throw your custom errors inside your conditions and handle them here
}
catch {
    // catch general errors
}

// or just attempt a change
do {
    try stateMachine.changeTo(.Loading, userInfo: nil) // succeeds
    try stateMachine.changeTo(.Loaded, userInfo: nil) // will check 'mySuccessCheck'
}
catch MyCustomError.CustomErrorOne {
    // handle your custom error case
}
catch {
    // handle a general error
}

Documentation

Full docs here

Requirements

  • iOS 8.1+ / Mac OS X 10.9+
  • Xcode 7

Installation

Embedded frameworks require a minimum deployment target of iOS 8 or OS X Mavericks.

To use SCAStateMachine with a project targeting iOS 7 or to include it manually, you must include the 'StateMachine.swift' file located inside the Source directory directly in your project

CocoaPods

To integrate SCAStateMachine into your Xcode project using CocoaPods, specify it in your Podfile:

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
use_frameworks!

pod 'SCAStateMachine', :git => 'https://github.com/seancatkinson/SCAStateMachine.git'

Once Cocoapods supports Swift 2.0 I'll submit as an actual pod.

License

SCAStateMachine is released under the MIT license. See LICENSE for details.

About

A lightweight state machine built in Swift

License:MIT License


Languages

Language:Swift 97.6%Language:Objective-C 1.4%Language:Ruby 0.7%Language:Shell 0.3%