Implementing all states through structs, and moving the logic

Each state will be responsible for its own logic and transitions, so we can start moving the logic into each state:

struct UnknownState: CardReaderState {
static let shared = UnknownState()
func perform(context: CardReader) {
// Perform local initialization, When everything is ready
// Toggle the state to Waiting
context.state = WaitingState.shared
}
}

As you can see, this is cleaner, as our initialization states are encapsulated. In the example code following, the rest of the logic is even more encapsulated:

struct WaitingState: CardReaderState {
static let shared = WaitingState()
func perform(context: CardReader) {
guard seenCard() else { return }
// we have seen a card!
context.state = ReadingState.shared
}
}

struct ReadingState: CardReaderState {
func perform(context: CardReader) {
// Read the contents of the card over the radio
// This is quite complext and can take a while
if readCard() {
// Card was read
context.state = ReadState(card: CardInfo())
} else {
context.state = ErrorState(error: ReadError())
}
}
}

struct ReadState: CardReaderState {
let card: CardInfo
func perform(context: CardReader) {
// Open the gates to the metro doors
// And reset back to waiting
context.state = WaitingState.shared
}
}

struct ErrorState: CardReaderState {
let error: Error
func perform(context: CardReader) {
// display an error, and go back to waiting
context.state = WaitingState.shared
}
}