StateMachine-GCDThreadsafe 2.0.2

StateMachine-GCDThreadsafe 2.0.2

TestsTested
LangLanguage Obj-CObjective C
License MIT
ReleasedLast Release Dec 2014

Maintained by Unclaimed.



 
Depends on:
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
 

  • By
  • Luis Solano Bonet and bryn austin bellomy

// 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.

features

  • 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.

installation

  1. CocoaPods is the way and the light. Just add to your Podfile:

    pod 'StateMachine-GCDThreadsafe', '>= 2.0.0'
  2. 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.

  3. 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;
@end

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
@gcd_threadsafe

// 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:@"suspended"];
    [sm addState:@"terminated"];

    [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;
@end

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

contributing

  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