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 |
Grand Central Dispatch-backed threadsafe state machine library for iOS.
This library was inspired by the Ruby gem state_machine and the letter 5.
before
and after
transition hooks.CocoaPods is the way and the light. Just add to your Podfile
:
pod 'StateMachine-GCDThreadsafe', '>= 2.0.0'
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.
git submodule
.Seriously though, get with CocoaPods already, y'know?
Let's model a Subscription
class.
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
@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. ...
In the implementation of the class, we use the StateMachine
DSL to define our valid states and events.
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];
}];
});
@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:
[self initializeStateMachine]
right after that.- (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.
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).
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"
duh, son, c'mon now