• Stars
    star
    283
  • Rank 146,066 (Top 3 %)
  • Language
    Swift
  • License
    MIT License
  • Created over 10 years ago
  • Updated over 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Modern finite-state machine implemented in pure Swift

Build Status Β  codecov.io CocoaPod platform Β  CocoaPod version Β  Carthage compatible Packagist Transporter

Transporter is a modern finite-state machine implemented in pure Swift. It is truly cross-platform, and supports iOS, OS X, tvOS, watchOS, Linux.

Features

  • Simple mode, allowing to manually switch states
  • Strict mode, allowing switching states only with Events and proper Transition
  • Closure(block)-based callbacks on states and events
  • Generic implementation allows using any State values
  • Unit-tested and reliable

Classic turnstile example

enum Turnstile {
    case Locked
    case Unlocked
}

let locked = State(Turnstile.Locked)
let unlocked = State(Turnstile.Unlocked)

locked.didEnterState = { _ in lockEntrance() }
unlocked.didEnterState = { _ in unlockEntrance() }

let coinEvent = Event(name: "Coin", sourceValues: [Turnstile.Locked], destinationValue: Turnstile.Unlocked)
let pushEvent = Event(name: "Push", sourceValues: [Turnstile.Unlocked], destinationValue: Turnstile.Locked)

let turnstile = StateMachine(initialState: locked, states: [unlocked])
turnstile.addEvents([coinEvent,pushEvent])

turnstile.fireEvent("Coin")
turnstile.isInState(.Unlocked) //true

States

Due to generic implementation, you can have StateMachine of any type you want. The only requirement for state values is they should be Hashable. So, you can have Int State, or String State etc. Or have value of enum, like it's shown in example.

let intState = State(0)
let stringState = State("foo")
let enumState = State(Turnstile.Locked)

Getting states

  let state = machine.stateWithValue(4)

Adding states

  machine.addState(state)
  machine.addStates([state1,state2])

You can also use convenience constructor:

  let machine = StateMachine(initialState: initialState, states: [state1,state2])

Events

Adding events implicitly checks, whether event source states and destination state are present in StateMachine. If states are not present, event will not be added to StateMachine.

  _ = try? machine.addEvent(event)
  machine.addEvents([event1,event2])

Can event be fired?

  if machine.canFireEvent("foo") {
    println("Fire it!")
  }

Transitions

When event is fired, StateMachine returns Transition object, that you can react to

  let transition = machine.fireEvent("Coin")
  switch transition {
  case .Success(let sourceState, let destinationState):
    println("Successful transition from state: \(sourceState) to state: \(destinationState)")
  case .Error(let error)
    println("Failed to transition with error: \(error)")
  }

Switching states manually

Transporter supports canonic finite state machine principle, that disallows transitions, if they are not defined in events StateMachine has, but sometimes you would want something simpler. Transporter gives you ability to switch states manually without actually creating any events.

  let initial = State("Initial")
  let machine = StateMachine(initialState: initial)
  machine.addState(State("Finished"))

  machine.activateState("Finished")
  machine.isInState("Finished") // true

Objective-C

Due to generic implementation of Transporter, it will not support Objective-C. If you are looking for state machine, written in Objective-C, i recommend great TransitionKit library by Blake Watters.

Requirements

  • iOS 8
  • Mac OS 10.10
  • watchOS 2
  • tvOS 9.0
  • Swift 3
  • XCode 8

Installation

CocoaPods

  pod 'Transporter', '~> 3.0.0'

Carthage

  carthage 'DenHeadless/Transporter' "3.0.0"