Git Product home page Git Product logo

statemachine-gcdthreadsafe's Introduction

// 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'
  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;
@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:@"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;
@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.

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 problem.
  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).

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

statemachine-gcdthreadsafe's People

Contributors

luisobo avatar brynbellomy avatar

Stargazers

Gustavo avatar shenchen avatar Slipp Douglas Thompson avatar Jiyee Sheng avatar Steven Koposov avatar ShiningChan avatar kim4apple avatar Timur Kuchkarov avatar nash_su avatar  avatar Lin Chia Hua avatar Jonathan Dalrymple avatar Michael avatar Maxim Khatskevich avatar Roman Busygin avatar August Jaenicke avatar  avatar Cora Middleton avatar Cody A. Ray avatar  avatar Oskar Stephens avatar David O'Callaghan avatar  avatar David Quon avatar  avatar Indrajit Chakrabarty avatar Steven Zhang avatar Song lanlan avatar Luís Rodrigues avatar Jeffrey Macko avatar Ryan Nielsen avatar Andrew Wooster avatar Dave Smith avatar Giovanni Scarrone avatar Georg Kitz avatar Simon Maddox avatar Werner Ramaekers avatar matiaz avatar Eli Perkins avatar Aron Cedercrantz avatar  avatar Peter Zhang avatar Jonathan Crooke avatar Chen Yihu avatar Flori avatar Pierre Dulac avatar Daniel Tull avatar Johannes Plunien avatar  avatar YangLe avatar Romain Briche avatar Andreas Schubert avatar Alex Akers avatar Jon Buffington avatar Matthias Tretter avatar Mathijs Kadijk avatar Kitsion avatar Vladislav Alekseev avatar Jonah avatar Paul Armstrong avatar Yasuhiro Inami avatar mayulu avatar Xinyu He avatar Cleber Santos avatar Matt Andersen avatar Ilya Stroganov avatar Julien Fantin avatar Kirill Kunst avatar Jesse Wolff avatar caiguo avatar Vincent Esche avatar @thingsdoer avatar Chris Walters avatar Krzysztof Zabłocki avatar Mattes Groeger avatar Willi Wu avatar

Watchers

Flori avatar Matt Farrugia avatar mayulu avatar  avatar August Jaenicke avatar  avatar James Cloos avatar  avatar

Forkers

orta takenjiridho

statemachine-gcdthreadsafe's Issues

Does this work without CocoaPods?

I'm supporting a team and I'd rather just use Git. But I couldn't get libPod.a to build. I assume this is because It requires cocoaPods to retrieve the dependancy?

Anychane it could be runnable with straight git or is there any advice to getting it to work?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.