Latest 2.0.2
License MIT
Platforms ios 5.1, requires ARC
Dependencies libextobjc/EXTScope, libextobjc/EXTSynthesize, libextobjc/EXTBlockMethod, libextobjc/NSMethodSignature+EXT, BrynKit/Main, BrynKit/GCDThreadsafe
Authors ,

// StateMachine-GCDThreadsafe

Grand Central Dispatch-backed threadsafe state machine library for iOS.

This library was inspired by the Ruby gem state_machine and the letter 5.


  • Threadsafe and FAST. All transition code executes within barrier blocks on a critical-section-only async dispatch queue.
  • Open architecture. You can submit your own blocks to this queue as well. Everything will be threadsafed for you under the hood.
  • Easy, block-based DSL for defining your classes’ state machines. Block-based before and after transition hooks.
  • Less boilerplate to write. Dynamically generates all state machine methods directly onto your classes using some Objective-C runtime voodoo jah.


  1. CocoaPods is the way and the light. Just add to your Podfile:
pod 'StateMachine-GCDThreadsafe', '>= 2.0.0'
  1. The direct approach. You should be able to add StateMachine to your source tree. Create an Xcode workspace containing your project and then import the StateMachine-GCDThreadsafe project into it.
  2. The indirect approach. If you are using git, consider using a git submodule.

Seriously though, get with CocoaPods already, y’know?

basic usage for humans with an concrete objective

Let’s model a Subscription class.

1. @interface

Declare your class to conform to the SEThreadsafeStateMachine protocol (which is defined in
LSStative.h, if you’re curious).

@interface Subscription : NSObject <SEThreadsafeStateMachine>
@property (nonatomic, strong, readwrite) NSDate *terminatedAt;
- (void) stopBilling;

2. the @gcd_threadsafe macro.

@gcd_threadsafe is defined in <BrynKit/GCDThreadsafe.h>. Import that. The macro itself should be placed within your @implementation block. Preferably at the very top, so it’s more self-documenting.

This macro defines a couple of methods on your class for dispatching critical section code onto self.queueCritical — the show-stopper, the main attraction — the private dispatch_queue_t on which shit be GITTAN RIAL.

#import <BrynKit/GCDThreadsafe.h>

@implementation Subscription

// methods, etc. ...

3. the state machine dsl

In the implementation of the class, we use the StateMachine DSL to define our valid states and events.

  1. "The DSL is a work in progress and will change."@luisobo
  2. "I’onno mate I think i’ss quite nice, really"@brynbellomy
  3. Conclusion: *shrug*
STATE_MACHINE(^(LSStateMachine *sm) {
    sm.initialState = @"pending";

    [sm addState:@"pending"];
    [sm addState:@"active"];
    [sm addState:@"waitingOnSomething"];
    [sm addState:@"suspended"];
    [sm addState:@"terminated"];

    [sm when:@"startWaiting" transitionFrom:@"active" to:@"waitingOnSomething"];
    [sm when:@"activate"  transitionFrom:@"pending" to:@"active"];
    [sm when:@"suspend"   transitionFrom:@"active" to:@"suspended"];
    [sm when:@"unsuspend" transitionFrom:@"suspended" to:@"active"];
    [sm when:@"terminate" transitionFrom:@"active" to:@"terminated"];
    [sm when:@"terminate" transitionFrom:@"suspended" to:@"terminated"];

    [sm before:@"terminate" do:^(Subscription *self){
        self.terminatedAt = [NSDate dateWithTimeIntervalSince1970:123123123];

    [sm after:@"suspend" do:^(Subscription *self) {
        [self stopBilling];

4. designated initializer

  1. Use the @gcd_threadsafe_init(...) macro in your designated initializer (-init, etc.). Place it before anything else in the if (self) block. It takes two parameters, both of which concern the critical section dispatch queue:
    • its concurrency mode: SERIAL or CONCURRENT
    • and its label: a regular C string
  2. Call [self initializeStateMachine] right after that.
  3. C’mon just do it
- (id) init
    self = [super init];
    if (self) {
        @gcd_threadsafe_init(CONCURRENT, "com.pton.KnowsHowToParty.queueCritical");
        [self initializeStateMachine];
    return self;

@gcd_threadsafe_init(...) initializes the self.queueCritical property.

the metamorphosis

Once given a particular configuration of transitions and states, StateMachine-GCDThreadsafe will dynamically add the appropriate methods to your class to reflect that configuration. You’ll find yourself facing a few compiler warnings regarding these methods. Wanna shut the compiler up? Easy enough: define a class category and don’t implement it. The category can live hidden inside your implementation file (if the methods need to be private), in your header file (if the methods ought to be publicly callable), or split between the two.

@interface Subscription (StateMachine)
- (void)initializeStateMachine;
- (BOOL)activate;
- (BOOL)suspend;
- (BOOL)unsuspend;
- (BOOL)terminate;

- (BOOL)isPending;
- (BOOL)isActive;
- (BOOL)isSuspended;
- (BOOL)isTerminated;

- (BOOL)canActivate;
- (BOOL)canSuspend;
- (BOOL)canUnsuspend;
- (BOOL)canTerminate;

In addition to your class’s main state property being KVO-observable, StateMachine will define query methods to check if the object is in a given state (isPending, isActive, etc.) and to check whether an event will trigger a valid transition (canActivate, canSuspend, etc).

triggering events

Subscription *subscription = [[Subscription alloc] init];
subscription.state;                 // will be set to @"pending", the value of the initialState property

Start triggering events…

[subscription activate];            // retuns YES because it's a valid transition
subscription.state;                 // @"active"

[subscription suspend];             // also, `-stopBilling` is called by `-suspend`
                                    // retuns YES because it's a valid transition

subscription.state;                 // @"suspended"

[subscription terminate];           // retuns YES because it's a valid transition
subscription.state;                 // @"terminated"

subscription.terminatedAt;           // [NSDate dateWithTimeIntervalSince1970:123123123];

But! If we trigger an invalid event…

// the subscription is now suspended

[subscription activate];            // returns NO because it's not a valid transition
subscription.state;                 // @"suspended"

is it re-entrant?

duh, son, c’mon now.

tips n’ tricks

  1. It’s almost always a BAD, BAD idea to dispatch_sync(...) a synchronous block to the main queue from inside
    one of your transition or critical section blocks. Why? If the main thread happens to be waiting on your
    critical section code before moving forward, you’ll deadlock. You should generally be sending things to the
    main queue that don’t need to be synchronous (UI updates, certain kinds of NSNotifications, KVO messages, etc.).
    If it seems impossible to rewrite your main thread code in an asynchronous way, you may have an architectural
  2. If you’re implementing a GCD-threadsafe StateMachine on one of your UIViewControllers, keep in mind
    all of UIViewController‘s -viewDidX... and -viewWillY... methods are called from the main thread. Given
    tip #1 just above, this means that you have to be especially careful of deadlocks in UIViewController state
    machines (and in GCDThreadsafe code inside these UIViewController methods more generally).


  1. Brush up on your ReactiveCocoa. That’s the direction that this fork of the code is overwhelmingly likely to head.
  2. Fork this project.
  3. Create a new feature branch.
  4. Commit your changes.
  5. Push to the branch.
  6. Create new pull request.

top scores

Latest podspec

    "name": "StateMachine-GCDThreadsafe",
    "platforms": {
        "ios": "5.1"
    "version": "2.0.2",
    "summary": "Threadsafe state machine library for Objective-C based on Grand Central Dispatch.",
    "homepage": "",
    "license": {
        "type": "MIT",
        "file": "LICENSE"
    "authors": {
        "Luis Solano Bonet": "[email protected]",
        "bryn austin bellomy": "[email protected]"
    "source": {
        "git": "",
        "tag": "v2.0.2"
    "source_files": "StateMachine/**/*.{h,m}",
    "public_header_files": [
    "requires_arc": true,
    "dependencies": {
        "libextobjc/EXTScope": [
            ">= 0.2.5"
        "libextobjc/EXTSynthesize": [
            ">= 0.2.5"
        "libextobjc/EXTBlockMethod": [
            ">= 0.2.5"
        "libextobjc/NSMethodSignature+EXT": [
            ">= 0.2.5"
        "BrynKit/Main": [
            ">= 1.2.1"
        "BrynKit/GCDThreadsafe": [
            ">= 1.2.1"

Pin It on Pinterest

Share This