State of the art state machine
Almost two years ago, I wrote about organization and architecure of my Xcode projects. As all, I also envolved a lot during these few years. If nothing else, I released my first shareware application and I learned a lot from it. In this post, I’ll describe some of the changes to how I architecture my applications.
Introduction
Comparing my previous setup and current one seems like day and night. Although I was experienced programmer for other platforms then, I was still relatively new to Objective C and Cocoa, so some of the things weren’t that natural to me or I simply didn’t realize how I could port my knowledge from other platforms. In this time I also jumped from Leopard API to Snow Leopard and Lion, both bringing a lot of improvements that I rely on in my daily work now.
Speaking about all of the changes would extend the scope of a single blog post. Perhaps I’ll write more posts related to the matter, but in this one I’d like to concentrate on application delegate and main application states. I’ll use Startupizer as an example, but will do my best to keep it as generic as possible, so you should be able to apply the concepts to your app, be it Cocoa or Cocoa Touch.
From spagetti code…
Startupizer has lot’s of states it can be in: searching for missing items, executing items, checking for changed in System Preferences etc. As these are all application-wide states, handling them in application delegate seemed natural. So my delegate code looked something like this:
- (void)applicationDidFinishLaunching:(NSNotification *)note {
// Hook up all notifications
[notificationCenter addObserver:self
selector:@selector(searchForMissingItemsDidEnd:)
name:GBMissingItemsSearchDidEnd
object:nil];
...
// Start search for missing items (will eventually post notification)
...
}
- (void)searchForMissingItemsDidEnd:(NSNotification *)note {
// Do whatever needed
...
// Start executing items (will eventually post notification)
...
}
- (void)itemsExecutionDidEnd:(NSNotification *)note {
// Do whatever needed
...
// Start with next state (will eventually post notification)
...
}
...
Although individual states were embedded within their specific classes or NSOperation
s it was hard to understand how they were related. Combine this with other stuff the app delegate had to implement and possibly conditional logic in terms “start next state only if something is true” and you can imagine it became pretty tought to even understand the flow, not to mention maintain and debug it. Spagetti code at its best…
…and some thinking about it…
Starting work on next major update for Startupizer, I was looking for a better solution to this problem. Often when I find myself in looking for an architecture problem, I give myself a break and go through my trusty Gang of Four book. And as usual, there it was, standing right in front of my nose - the state pattern. I’ve used it countless times already for different purposes, but it just didn’t click to me I could also use it for handling application main states (there you go, I even call them states and yet failed to see the connection :)
Just briefly about state pattern: it has two primary classes: a state (and it’s subclasses) and a context. The context is the one that ties states together - it usually contains a list of all possible states and holds the link to currently active state. At least that’s how I implement it, I’ve seen implementations that rely on states being singletons, but I don’t like that solution. States on the other hand implement concrete functionality.
Important: If you’re not familiar with state pattern (or design patterns in general), I strongly recommend you go and do some research - there are plenty of sites or books available on the topic. In the following text I will assume you have at least basic understanding of the matter…
Anyway, I immediately took paper and pencil and scratched out rough idea: I wanted to keep state context outside application delegate, so decided to implement it as a separate class - delegate already has a bunch of other responsibilities. States seemed straightforward; usually each state knows the next state to execute once it’s finished - as every state is aware of it’s parent context, it can simply change the context’s current state. The context can optionally take care of stuff like notifying current state it’s about to seize being active state and informing the new state it’s become active. It can also post notification, so the rest of the application can update accordingly.
Seemed fine, so I started implementing it. It greatly reduced clutter in the app delegate and I was happy with myself. But, as always when I’m too happy :) - with new functionality being added to the application, I came to a limitation of my design. It basically boiled down to how next state was determined - this was baked into each state, but I needed to be more flexible and execute next state based on various conditions. For example: in normal conditions, I wanted to follow certain path, but if user cancelled an operation, I wanted to follow another. Furthermore, in some cases I wanted to execute the same state, but continue with a completely different path. At first I thought I could simply stuff all state-change conditional logic into each individual state, but it seemed this would break encapsulation.
So there I was, back to drawing board. I really wanted the application code decide the order of the states - this would really bring conditional logic to minimum, which is good thing. But bringing state switching outside the state raised the question of how to know when the state is done? Thankfully I was using the “callback” concept for a while now, so it didn’t take long for me to realize it: blocks!
…to “state of the art” state machine
Basically the solution boiled down to: when executing a state, pass it completion block which the state should execute when it’s done. Then in the caller (which is usually application delegate or one of the view/window controllers responding to user action), just execute next state in the completion block. And if needed, execute next one and so on. I still included the context class which held together all states, but there was no longer the concept of “current state” as that is now part of the caller.
In it’s basics, here’s how my context class looks like:
@interface ApplicationContext : NSObject
- (void)executeState:(ApplicationState *)state
completionBlock:(StateCompletionBlock)handler;
@property (nonatomic, strong) ApplicationState *state1;
@property (nonatomic, strong) ApplicationState *state2;
...
@end
And the state:
@interface ApplicationState : NSObject
- (void)executeWithCompletionBlock:(StateCompletionBlock)handler;
@end
State completion block is simply a block with no arguments and no return:
typedef void(^StateCompletionBlock)(void);
This was working really well. Not only did each individual state encapsulate its behavior, the application got complete freedom in deciding the order of states progression for whatever task it was about to accomplish. It all resulted in kind of Lego-like approach: simple and maintanable building blocks put together. Here’s an example of how it would be used:
- (void)applicationDidFinishLaunching:(NSNotification *)note {
ApplicationContext *context = this.applicationContext;
[context executeState:context.state1 completionBlock:^{
[context executeState:context.state2 completionBlock:^{
[context executeState:context.state3 completionBlock:^{
}];
}];
}];
}
Simple and straighforward and now the app has full control of how the states are executed! You could easily put some conditional logic in there too like this:
- (void)applicationDidFinishLaunching:(NSNotification *)note {
ApplicationContext *context = this.applicationContext;
[context executeState:context.state1 completionBlock:^{
[context executeState:context.state2 completionBlock:^{
if (!condition) return;
[context executeState:context.state3 completionBlock:^{
}];
}];
}];
}
Works like a charm! But there was just another demand I required: [ApplicationContext executeState:completionBlock:]
starts executing the given state immediately, followed by “substates” as expressed in completion block. But I really wanted to wait with new “batch” until previous one is finished. To keep application code simple, ApplicationContext
should keep the list of all “batches” and execute them in order received. So what I’d really like is some kind of FIFO. For this, I introduced another method that should be called as the “batch” start and use the execute method afterwards. I came up with this:
- (void)applicationDidFinishLaunching:(NSNotification *)note {
ApplicationContext *context = this.applicationContext;
[context pushState:context.state1 completionBlock:^{
[context executeState:context.state2 completionBlock:^{
[context executeState:context.state3 completionBlock:^{
}];
}];
}];
}
I simply call the “start batch” mehod pushState:completionBlock:
. It takes the same arguments, but it works differently: instead of executing the given state immediately, it “pushes” it into the FIFO and executes it after all previous requests are done. And working with blocks and GCD, saying “FIFO” should really ring a bell - it’s implemented with a simple GCD serial queue that takes care of all the “behind the scenes” for us! Mission accomplished!
I tend to keep a single instance of ApplicationContext
per application. Similarly, its states are lazily instantiated in property accessors and are then reused. Until now I didn’t encounter a problem with this setup - due to making sure I always push the first state and execute remaining ones and preventing pushing additional states while current batch is executing, only a single state is being executed at any given moment. But in case you need more versatile solution with many states executing simultaneously, you’d either need to create different context instances per each operation or share the same instance but have state accessors return new instances on each call.
Conclusion
Many times the simplest solution is at our finger tips: we work with it every day and yet fail to recognize its usage in a different set of challenges. But once we do, looking back we only wonder how on earth we couldn’t connect all the dots… Hopefully this article will help connecting few small dots in other developers lives…
Important: Note that the given imlementation executes new batch as soon as you push it to the context. It doesn’t wait until current batch is finished. To pull this off, you’d have to use barrier blocks or other means to prevent queue from executing subsequent batches until previous one is finished. I leave that as an excercise for readers, but feel free to let me know if you have found smart solution and I’ll update the post/code for other folks too!