This post tells the story behind SwiftFSM.
SwiftFSM is a state machine implemented in Swift 3. It is generic and supports callbacks for state transitions. It is extremely light-weight and the code is well-documented for any alterations or debugging.
This story outlines the motivation to create a state machine in Swift and the reasoning behind why it is developed the way it is.
I started building a game on iOS a while ago. It’s not a graphics-intensive storyline-based game. But, a simple, interactive kind. I initially thought that I wouldn’t need a state machine to handle all the possible outcomes and states as the game is too simple. But, as time went by, and I fixed on a concrete architecture for the app, I realised that it was horrible to handle the various transitions in states across different classes and reevaluated the possible use of a state machine.
I started looking into many open-source state machine libraries developed by others in Swift. I shortlisted 3 of them and every one of those was very good in its own way. They had advanced features like generating graphs and had their own operators for state change triggers and so on. But most of these features were not useful for me, my state diagram was written on paper and I was most comfortable with that to refer to. I had no use for the diagram generated by the library.
I also needed my state machine to be familiar in the way that I use it. I was already going back on how I had implemented transition logic across the app, the last thing I needed was a set of unfamiliar operators.
The other problem I had with a few libraries were that they were not yet ported to Swift 3. I tried porting the, myself, but, I gave up as it was taking up time to read through and modify certain things. That is when I decided to build one on my own — A simple, extremely light-weight and generic finite state machine.
I needed my state machine to be generic and be type-agnostic. I wanted it to work like Swift Arrays. Swift Arrays can be subscripted no matter what type each element of the Array is. They can also be appended to, iterated through and so much more all because Swift Arrays are generic. Similarly, I needed my state machine to handle states and triggers of any kind I choose to make them. This would give me a lot of freedom when using the state machine for my application.
The most common type of states and triggers would be enums, as enums represent a limited possible set of states and are very powerful in Swift. But, you could also have the triggers as Int, which brings about a whole new set of functionality because Integers can undergo math operations. Or, they could be String. The possibilities are limitless. All of this is possible with SwiftFSM.
Using Int for State makes it an infinite state machine. But, the name of the library was chosen to represent the most used case of State Machines where the States are finite.
I needed one location to handle all transitions of states in the machine. This would help in writing the transition logic and the consequent operations once and not worry about them again. And, debugging would also be easier if this was the case.
I prefer callbacks using closures instead of a delegate pattern for communication between dependent objects in most cases. With closures, I know what I’m assigning the closure to and it keeps the flow connected. But, with delegates, tracing from where the method gets called takes up slightly more effort. And, you cannot see which class the method in a delegate is getting called from in plain sight. This is important for me when I’m reading and debugging code.
That is why, as a personal choice, I chose to use closures to give information about state changes that happen in the machine.
As much as I’d like to sit and plan every possible scenario into my state diagram, there are certain conditions that I wouldn’t want to be a part of my state machine. I like my state machine to represent the operation of a scenario and only that. I prefer to leave out external conditions like network availability, change in phone orientation, sudden resigning of active of the app and such things out of my state machine. This reduces the complexity of the machine.
Leaving these triggers out should not mean that you shouldn’t handle them. That is why I needed a way to allow or disallow a state change after a trigger has been made and just before the machine changes state. This is done using a callback which returns a Bool in which you can write your logic pertaining to special cases where you’d not want to allow a state change to happen.
Caution: This feature should be avoided and every effort should be made to incorporate all states and triggers within the state machine. It is only provided as a fallback to use if you need it at any cost.
I needed my schema not to be defined as a concrete class or structure in the machine implementation, but, as a protocol just specifying the requirements to be valid as a schema object for SwiftFSM.
Protocols are super cool! I love them, and I actively find use cases to use protocols instead of inheritance. By making the SwiftFSM accept any object conforming to a protocol, I give the external user the freedom to have a lot more in the protocol object without subclassing. Subclassing introduces hierarchy, which is unnecessary in most cases. It is definitely a way to add functionality, but, the idea of saying that an object can act as a schema because it conforms to a protocol suits better.
Consider this: I’m declaring myself as a Writer, a Developer and a Dancer. Three different behaviours, but still a single person. If subclassing was involved here, it would mean I’d need to be a Writer and only my child and I together could be a Writer and a Developer and my grandchild, my child and I all together could be all three. It’s how we’ve done things all along, but, protocols are suited better here in my opinion, as the object is I alone, and I conform to three protocols Writer, Developer and Dancer and suddenly, I can be addressed by any one or all of them, while still being just one object.
The main advantage of having the schema as a protocol is that it allows me to create one struct object which could conform to the schema protocol and also allow me to handle what happens when a state change occurs all within the same structure. This keeps the transition logic and what happens when a certain transition happens all in one place, making the code cleaner and the debugging easier.
I needed my machine to log transitions and other events automatically. I also needed it to support any custom or third-party logging library that an external user may use in their project. That’s why, SwiftFSM can log using print statements internally and by default or allow the user to provide a closure in which the user can use any logging mechanism with the information provided by SwiftFSM as a parameter to the closure. If it’s preferred not to log anything, that’s an option, too.
It took me about three hours to plan and start with the state machine and get a fully-fuctional machine. Adding comments and marking the code up took another hour or so. The idea was to make the machine generic by using associated types for states and triggers in the schema protocol. I also wanted to provide a very straight-forward machine with a small learning curve for anybody to use.
Gameplay states management
As a router for complex navigation between ViewControllers
Handling custom animations
Validation of forms
Basically, anything can be modelled as an FSM. The initial setup could take up more effort, but, the clarity it would offer during development and debugging is something to consider. This is especially strong when you’re dealing with something with a complex set of operations.
I hope you find good use for SwiftFSM. If you haven’t already checked the GitHub Repository, here’s the link: https://github.com/vishalvshekkar/SwiftFSM
It’s provided with an MIT license. The README.md file explains the installation procedure.
Please feel free to add features, fix bugs or raise any issues on GitHub. I will be open for pull requests.
I write about everything I find interesting and worthy enough to share. Recommend this article if you feel more people should read it and follow me to read my future stories.
You may follow me on Twitter here.