StateMachine

by luisobo

luisobo / StateMachine

State machine library for Objective-C

206 Stars 28 Forks Last release: Not found MIT License 37 Commits 2 Releases

Available items

No Items, yet!

The developer of this repository has not created any items for sale yet. Need a bug fixed? Help with integration? A different license? Create a request here:

StateMachine

State machine library for Objective-C

This library was inspired by the Ruby gem state_machine.

Features

  • DSL for defining the state machine of your classes
  • Dynamically added methods to trigger events in the instances of your classes
  • Methods to query if an object is in a certain state (isActive, isPending, etc)
  • Methods to query wheter an event will trigger a valid transition or not (canActive, canSuspend, etc)
  • Transition callbacks. Execute arbitrary code before and after a transition occurs.

Installation

As a CocoaPod

Just add this to your Podfile

ruby
pod 'StateMachine', '~> 0.1'

Other approaches

  • You should be able to add StateMachine to you source tree. If you are using git, consider using a
    git submodule

Usage

Defining the state machine of a class

Let's model a Subscription class

At this moment you are responsible of defining a state property like this. In the future this will be handle by StateMachine

@interface Subscription : NSObject
@property (nonatomic, retain) NSString *state; // Property managed by StateMachine

@property (nonatomic, retain) NSDate *terminatedAt;

  • (void) stopBilling; @end

Here is the fun part. In the implementation of the class we use the StateMachine DSL to define the valid states and events. For each event you define which are the valid transitions.

The DSL is a work in progress and will change

You also have to include a call to

initializeStateMachine
in you constructor(s) for the moment. The goal is to remove this limitation and be less intrusive.
@implementation Subscription

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 *subscription){
    subscription.terminatedAt = [NSDate dateWithTimeIntervalSince1970:123123123];
}];

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

});

  • (id)init { self = [super init]; if (self) {

      [self initializeStateMachine];

    } return self; }

  • (void) stopBilling { // Yeah, sure... }

@end

StateMachine will methods to your class to trigger events. In order to make the compiler happy you need to tell it that this methods will be there at runtime. You can achieve this by defining the header of an Objective-C category with one method per event (returning BOOL) and the method

initializeStateMachine
. Just like this:
@interface Subscription (State)
- (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

As you can see, StateMachine will define query methods to check if the object is in a certain state (isPending, isActive, etc) and to check whether an event will trigger a valid transition (canActivate, canSuspend, etc).

Triggering events

Now you can create instances of your class as you would normally do

Subscription *subscription = [[Subscription alloc] init];

It has an initialState of

pending
subscription.state; // @"pending"

You can trigger events ```objc [subscription activate]; // retuns YES because it's a valid transition subscription.state; // @"active"

[subscription suspend]; // retuns YES because it's a valid transition // Method stopBilling was called subscription.state; // @"suspended"

[subscription terminate]; // retuns YES because it's a valid transition subscription.state; // @"terminated" subcription.terminatedAt; // [NSDate dateWithTimeIntervalSince1970:123123123]; ```

If we trigger an invalid event

objc
// The subscription is now suspended
[subscription activate]; // retuns NO because it's not a valid transition
subscription.state; // @"suspended"

Contributing

  1. Fork it
  2. Create your feature branch
  3. Commit your changes
  4. Push to the branch
  5. Create new Pull Request

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.