Skip to content

Commit

Permalink
Merge branch 'LABSM-16-final-touches' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
Maciej Konieczny committed Apr 28, 2015
2 parents d5ceca0 + af244db commit 7b171ae
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 76 deletions.
64 changes: 32 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ for clarity and maintainability. Can create diagrams:
![example digraph](example-digraph.png)

- Version 0.1.0 (following [Semantic Versioning][])
- Developed and tested under Xcode 6.3β4 (Swift 1.2)
- Developed and tested under Swift 1.2 (Xcode 6.3)
- Published under the [MIT License](LICENSE)
- [Carthage][] compatible

Expand All @@ -31,12 +31,12 @@ Features
--------

- Diagrams that can be automatically saved in your repo each time you run
the app in a simulator
the app in the simulator
- Immutable, reusable state machine schemas
- Readable state and event names — no long prefixes
- Type safety: compile time errors when forgetting about a state or an
event when defining the schema, when passing an event from a different
state machine, etc.
- Type safety: errors will appear at compilation when a state or an event
are absent from schema, when passing an event from a different state
machine, etc.


Documentation
Expand All @@ -61,7 +61,7 @@ project. We recommend using [Carthage][Carthage add]:
Example
-------

In this example we're going to implement a simple state machine you've
In this example, we're going to implement a simple state machine you've
seen at the beginning of this file:

![example digraph](example-digraph.png)
Expand All @@ -78,14 +78,14 @@ enum Operation {
}
```

Next we have to specify the state machine layout. In SwiftyStateMachine
it means creating a schema. Schemas are immutable `struct`s that can be
Next, we have to specify the state machine layout. In SwiftyStateMachine,
that means creating a schema. Schemas are immutable `struct`s that can be
used by many `StateMachine` instances. They indicate the initial state
and describe transition logic how states are connected via events and
and describe transition logic, i.e. how states are connected via events and
what code is executed during state transitions.

Schemas incorporate three generic types: `State` and `Event`, which we
defined above, and `Subject`, which represents an object associated with
defined above, and `Subject` which represents an object associated with
a state machine. To keep things simple we won't use subject right now,
so we'll specify its type as `Void`:

Expand Down Expand Up @@ -114,10 +114,10 @@ let schema = StateMachineSchema<Number, Operation, Void>(initialState: .One) { (
}
```

You've probably expected nested `switch` statements after defining two
You probably expected nested `switch` statements after defining two
`enum`s. :wink:

To understand the above snippet, it's helpful to look at initializer's
To understand the above snippet, it's helpful to look at the initializer's
signature:

```swift
Expand All @@ -128,9 +128,9 @@ init(initialState: State,
We specify transition logic as a block. It accepts two arguments: the
current state and the event being handled. It returns an optional tuple
of a new state and an optional transition block. When the tuple is
`nil`, it indicates that there is no transition for given state-event
pair, i.e. given event should be ignored in given state. When the
tuple is non-`nil`, it specifies the new state that machine should
`nil`, it indicates that there is no transition for a given state-event
pair, i.e. a given event should be ignored in a given state. When the
tuple is non-`nil`, it specifies the new state that the machine should
transition to and a block that should be called after the transition.
The transition block is optional. It gets passed a `Subject` object
as an argument, which we ignored in this example by using `_`.
Expand Down Expand Up @@ -161,11 +161,11 @@ machine.didTransitionCallback = { (oldState, event, newState) in

OK, what about the diagram? SwiftyStateMachine can create diagrams in
the [DOT][] graph description language. To create a diagram, we have
to use `GraphableStateMachineSchema`, which has the same initializer as
to use `GraphableStateMachineSchema` which has the same initializer as
the regular `StateMachineSchema`, but requires state and event types to
conform to the [`DOTLabelable`][DOTLabelable] protocol. This protocol
makes sure that all elements have nice readable labels and that they are
present on the graph at all (there's no way to automatically find all
present on the graph (there's no way to automatically find all
`enum` cases):

[DOT]: http://en.wikipedia.org/wiki/DOT_%28graph_description_language%29
Expand Down Expand Up @@ -200,7 +200,7 @@ extension Operation: DOTLabelable {
}
```

Notice that above code doesn't use a `switch` statement in
Notice that the above code doesn't use a `switch` statement in the
`DOTLabelableItems` implementation. We won't get a compiler error after
adding a new case to an `enum`. To get some help from the compiler in
such situations, we can use the following trick:
Expand All @@ -210,7 +210,7 @@ static var DOTLabelableItems: [Number] {
let items: [Number] = [.One, .Two, .Three]

// Trick: switch on all cases and get an error if you miss any.
// Copy and paste following cases to the array above.
// Copy and paste the following cases to the array above.
for item in items {
switch item {
case .One, .Two, .Three: break
Expand All @@ -222,8 +222,8 @@ static var DOTLabelableItems: [Number] {
```

When our types conform to `DOTLabelable`, we can define our structure as
before, but using `GraphableStateMachineSchema`. Then we can print the
diagram:
before, but this time using `GraphableStateMachineSchema`. Then we can
print the diagram:

```swift
let schema = GraphableStateMachineSchema// ...
Expand All @@ -249,15 +249,15 @@ digraph {
```

[On iOS][] we can even have the graph file saved in the repo each time
we run the app in a simulator:
we run the app in the simulator:

[On iOS]: https://github.com/macoscope/SwiftyStateMachine/commit/9b4963c26a934915b56d5023f84e42ff128f6a1d

```swift
schema.saveDOTDigraphIfRunningInSimulator(filepathRelativeToCurrentFile: "123.dot")
```

DOT files can be viewed by a number of applications, including free
DOT files can be viewed by a number of applications, including the free
[Graphviz][]. If you use [Homebrew][], you can install Graphviz with
the following commands:

Expand All @@ -268,7 +268,7 @@ the following commands:
[Graphviz]: http://www.graphviz.org/
[Homebrew]: http://brew.sh/

Graphviz comes with a `dot` command, which can be used to generate graph
Graphviz comes with a `dot` command which can be used to generate graph
images without launching the GUI app:

dot -Tpng 123.dot > 123.png
Expand Down Expand Up @@ -317,18 +317,18 @@ class MyViewController: UIViewController {
Development
-----------

If you see a way to improve the project, please leave a comment, open an
[issue][] or start a [pull request][]. It's better to begin
with an issue rather than a pull request, though, because we might disagree
whether proposed change is an actual improvement. :wink:
If you see a way to improve the project, please leave a comment, open
an [issue][] or start a [pull request][]. It's better to begin with
an issue rather than a pull request, though, because we might disagree
whether the proposed change is an actual improvement. :wink:

To run tests, install [Carthage][] and run `carthage update` to download
and build test frameworks.

When introducing changes, please try to conform to the style present
in the project — both with respect to code formatting and commit
messages. We recommend following [GitHub Swift Style Guide][] with one
important difference: 4 spaces instead of tabs.
When introducing changes, please try to conform to the style present in
the project — both with respect to code formatting and commit messages.
We recommend following [GitHub Swift Style Guide][] with one important
difference: 4 spaces instead of tabs.

Thanks! :v:

Expand Down
43 changes: 23 additions & 20 deletions StateMachine/GraphableStateMachineSchema.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,24 @@ import Foundation
/// [1]: http://en.wikipedia.org/wiki/DOT_%28graph_description_language%29
public protocol DOTLabelable {

/// A textual representation of `self`, suitable for creating a label
/// A textual representation of `self`, suitable for creating labels
/// in a graph.
var DOTLabel: String { get }

/// Array of items of given type (states or events) used in a graph.
/// An array of items of a given type (states or events) used in a graph.
static var DOTLabelableItems: [Self] { get }
}


/// A state machine schema with a graph of the state machine in the DOT graph
/// description language [1].
///
/// Requires `State` and `Event` types to conform to `DOTLabelable` protocol.
/// Requires `State` and `Event` types to conform to the `DOTLabelable`
/// protocol.
///
/// Textual representation of a graph is accessible by `DOTDigraph` property.
/// On iOS, it can be saved to disk with `saveDOTDigraphIfRunningInSimulator`
/// method.
/// The textual representation of a graph is accessible via the
/// `DOTDigraph` property. On iOS, it can be saved to disk with
/// the `saveDOTDigraphIfRunningInSimulator` method.
///
/// For more information about state machine schemas, see
/// `StateMachineSchemaType` documentation.
Expand All @@ -50,28 +51,30 @@ public struct GraphableStateMachineSchema<A: DOTLabelable, B: DOTLabelable, C>:
}

#if os(OSX)
// TODO: Figure out how detect scenario "I'm running my Mac app from Xcode".
// TODO: Figure out how to detect the "I'm running my Mac app from Xcode"
// scenario.
//
// Verify if [`AmIBeingDebugged`][1] can be used here. In particular, figure out
// if this means that an app will be rejected during App Review:
// Verify if [`AmIBeingDebugged`][1] can be used here. In particular,
// figure out if this means that an app will be rejected during App Review:
//
// > Important: Because the definition of the kinfo_proc structure (in <sys/sysctl.h>)
// > is conditionalized by __APPLE_API_UNSTABLE, you should restrict use of the above
// > code to the debug build of your program.
// > Important: Because the definition of the kinfo_proc structure
// > (in <sys/sysctl.h>) is conditionalized by __APPLE_API_UNSTABLE, you
// > should restrict use of the above code to the debug build of your
// > program.
//
// [1]: https://developer.apple.com/library/mac/qa/qa1361/_index.html
#else
/// Save textual graph representation from `DOTDigraph` property to a file,
/// preferably in a project directory, for documentation purposes. Works
/// only when running in a simulator.
/// Save the textual graph representation from the `DOTDigraph` property
/// to a file, preferably in a project directory, for documentation
/// purposes. Works only when running in a simulator.
///
/// Filepath of the graph file is relative to the filepath of the calling
/// file, e.g. if you call this method from a file called
/// The filepath of the graph file is relative to the filepath of the
/// calling file, e.g. if you call this method from a file called
/// `MyProject/InboxViewController.swift` and pass `"Inbox.dot"` as
/// a filepath, diagram will be saved as `MyProject/Inbox.dot`.
/// a filepath, the diagram will be saved as `MyProject/Inbox.dot`.
///
/// Files in DOT format [1] can be viewed in different applications, e.g.
/// Graphviz [2].
/// Files in the DOT format [1] can be viewed in different applications,
/// e.g. Graphviz [2].
///
/// [1]: https://developer.apple.com/library/mac/qa/qa1361/_index.html
/// [2]: http://www.graphviz.org/
Expand Down
51 changes: 27 additions & 24 deletions StateMachine/StateMachine.swift
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
/// A type representing schema that can be reused by `StateMachine` instances.
/// A type representing schema that can be reused by `StateMachine`
/// instances.
///
/// Schema incorporates three generic types: `State` and `Event`, which should
/// be `enum`s, and `Subject`, which represents an object associated with
/// a state machine. If you don't want to associate any object, use `Void`
/// as `Subject` type.
/// The schema incorporates three generic types: `State` and `Event`,
/// which should be `enum`s, and `Subject`, which represents an object
/// associated with a state machine. If you don't want to associate any
/// object, use `Void` as `Subject` type.
///
/// Schema indicates the initial state and describes transition logic — how
/// states are connected via events and what code is executed during state
/// transitions. You specify transition logic as a block that accepts two
/// arguments: current state and event being handled. It returns an optional
/// tuple of a new state and an optional transition block. When the tuple is
/// `nil`, it indicates that there is no transition for given state-event
/// pair, i.e. given event should be ignored in given state. When the tuple
/// is non-`nil`, it specifies the new state that machine should transition to
/// and a block that should be called after the transition. The transition
/// block is optional and it gets passed a `Subject` object as an argument.
/// The schema indicates the initial state and describes the transition
/// logic, i.e. how states are connected via events and what code is
/// executed during state transitions. You specify transition logic
/// as a block that accepts two arguments: the current state and the
/// event being handled. It returns an optional tuple of a new state
/// and an optional transition block. When the tuple is `nil`, it
/// indicates that there is no transition for a given state-event pair,
/// i.e. a given event should be ignored in a given state. When the
/// tuple is non-`nil`, it specifies the new state that the machine
/// should transition to and a block that should be called after the
/// transition. The transition block is optional and it gets passed
/// the `Subject` object as an argument.
public protocol StateMachineSchemaType {
typealias State
typealias Event
Expand All @@ -27,8 +30,8 @@ public protocol StateMachineSchemaType {
}


/// A state machine schema conforming to `StateMachineSchemaType` protocol.
/// See protocol's documentation for more information.
/// A state machine schema conforming to the `StateMachineSchemaType`
/// protocol. See protocol documentation for more information.
public struct StateMachineSchema<A, B, C>: StateMachineSchemaType {
typealias State = A
typealias Event = B
Expand All @@ -44,11 +47,11 @@ public struct StateMachineSchema<A, B, C>: StateMachineSchemaType {
}


/// State machine for given schema, associated with given subject. See
/// A state machine for a given schema, associated with a given subject. See
/// `StateMachineSchemaType` documentation for more information about schemas
/// and subjects.
///
/// State machine provides the `state` property for inspecting the current
/// The state machine provides the `state` property for inspecting the current
/// state and the `handleEvent` method for triggering state transitions
/// defined in the schema.
///
Expand All @@ -57,15 +60,15 @@ public struct StateMachineSchema<A, B, C>: StateMachineSchemaType {
/// the state before the transition, the event causing the transition,
/// and the state after the transition.
public struct StateMachine<T: StateMachineSchemaType> {
/// Current state of the machine.
/// The current state of the machine.
public var state: T.State

/// Optional block called after a transition with three arguments:
/// An optional block called after a transition with three arguments:
/// the state before the transition, the event causing the transition,
/// and the state after the transition.
public var didTransitionCallback: ((T.State, T.Event, T.State) -> ())?

/// Schema of the state machine. See `StateMachineSchemaType`
/// The schema of the state machine. See `StateMachineSchemaType`
/// documentation for more information.
private let schema: T

Expand All @@ -80,8 +83,8 @@ public struct StateMachine<T: StateMachineSchemaType> {
}

/// A method for triggering transitions and changing the state of the
/// machine. If transition logic of the schema defines a transition
/// for current state and given event, the state is changed, optional
/// machine. If the transition logic of the schema defines a transition
/// for current state and given event, the state is changed, the optional
/// transition block is executed, and `didTransitionCallback` is called.
public mutating func handleEvent(event: T.Event) {
if let (newState, transition) = schema.transitionLogic(state, event) {
Expand Down

0 comments on commit 7b171ae

Please sign in to comment.