8. THEY CAN BE USEFUL FOR VARIOUS REASONS:
▸ They are simple to draw
▸ They are simple to understand
▸ They make the state of your application explicit
▸ They are easy to program
9. FOR THIS REASON, THEY CAN
BE A NICE WAY TO MODEL THE
BEHAVIOR OF YOUR
APPLICATION.
10. AN EXAMPLE API
var machine = StateMachine()
var loadingState = State(name: "Loading")
var emptyState = State(name: "Empty")
var displayState = State(name: "Display")
11. loadingState.addTransition(with: "loaded", to: emptyState) { state in
guard loadedModel.count > 0 {
machine.transition(with: "firstItemAdded")
return
}
tableView.backgroundView = EmptyMessageView()
}
emptyState.addTransition(with: "firstItemAdded", to: displayState) { state in
tableView.backgroundView = nil
tableView.reloadData()
}
displayState.addTransition(with: "onlyItemRemoved", to: displayState) { state in
tableView.backgroundView = EmptyMessageView()
}
displayState.addTransition(with: "itemAdded", to: displayState) { state in
// animate item addition
}
23. YOU MIGHT NOT NOTICE IT BY LOOKING AT A
LOT OF STATE MACHINES LIBRARIES, BUT IT
IS ACTUALLY VERY EASY TO MAKE STATES
INTO INSTANCES AND TRANSITIONS INTO
METHODS.
25. THIS WAY YOU CAN USE THE
COMPILER TO PREVENT THE
APPLICATION FROM EVER
ENTERING AN INCONSISTENT
STATE.
26. E.G. THE PREVIOUSLY DISCUSSED PROBLEM CAN'T HAPPEN
// Might cause an issue at runtime
machine.transition(with: "onlyItemRemoved")
// vs.
// Won't compile
state = loadingState.onlyItemRemoved()
28. class ViewController: UIViewController {
// This item is expensive to load.
// I show the view controller,
// then set the item property once
// it is loaded.
var item: Item?
}
33. I DON'T WANT TO USE AN IMPLICITLY-
UNWRAPPED OPTIONAL HERE BECAUSE I AM
INTERACTING WITH APPLE'S FRAMEWORKS.
34. I AM NOT SURE ABOUT THE TIMING OF THE
CALLS WHEN DEALING WITH A FRAMEWORK I
DON'T CONTROL.
35. protocol State {}
class LoadingState: State {
func loaded() -> EmptyState
}
class EmptyState: State {
// We know that data is not nil here, so it
// can have a non-optional reference
var item: Item
func oneItemAdded() -> DisplayState
}
// Display State could just be the tableView data source
class DisplayState: State, UITableViewDataSource {
var item: Item // We know that data is not nil here
func onlyItemDeleted() -> EmptyState
func itemAdded() -> DisplayState
}
41. var item: Item? {
didSet {
if let item = item, let loadingState = state as? LoadingState {
let emptyState = loadingState.loaded()
emptyState.item = item
self.state = emptyState
}
}
}
42. func addObject() {
// add object
if item.listOfSomething.count == 1, let emptyState = state as? EmptyState {
self.state = emptyState.oneItemAdded()
}
}
43. func deleteObject() {
// delete object
if item.listOfSomething.count < 1, let displayState = state as? DisplayState {
self.state = displayState.onlyItemDeleted()
} else {
// Problem: What if state is not a DisplayState?
}
}
44. DisplayState IS ACTUALLY THE TABLE VIEW DATA
SOURCE, SO THE ITEM IS NOT NIL OR EMPTY.
func tableView(_ tableView: UITableView, didSelectRow at indexPath: IndexPath) {
let something = item.listOfSomething[indexPath.row]
// ...
}
45. QUESTION: WAS THIS EXERCISE IN MAKING
EXPLICIT STATE NECESSARY? COULDN'T WE
JUST, FOR EXAMPLE, MAKE CHILD VIEW
CONTROLLERS AND TRANSITION IN BETWEEN
THEM?
46. CONCLUSION:
▸ In some cases we might be able represent view controller state
explicitly with a state machine.
▸ If the state instances have references to objects which must not be
nil in their scope, we might be able to avoid needing to unwrap a lot
of optionals. (More generally, we might be able to more closely
model the lifecycle of the object.)