Greetings, traveler!
There are moments in almost every app where state matters more than behavior. A user edits a form, navigates across screens, makes a few changes, and then expects to undo everything with a single action. Reconstructing that state from scratch can be expensive or simply impractical. That is where the Memento pattern fits naturally.
I keep coming back to this pattern when working with flows that need reliable rollback. It solves a very specific problem cleanly: preserving object state without exposing its internals.
What problem Memento solves
At its core, Memento allows an object to create a snapshot of its internal state and restore itself later. The important detail here is who owns the knowledge about that state.
The object itself knows how to save and restore. External code can store snapshots but should not inspect or modify them.
This separation keeps the system predictable. State stays encapsulated. External components coordinate, but they do not interfere.
Roles in the pattern
A typical implementation involves three roles:
- Originator — the object whose state needs to be saved
- Memento — an immutable snapshot of that state
- Caretaker — a storage that keeps mementos, often as a stack for undo/redo
It may seem tempting to merge some of these roles in simple examples. In practice, keeping responsibilities separate pays off quickly once the system grows.
A Swift implementation
Let’s start with a simple domain model.
final class Protagonist {
private(set) var currentLocation: String
private(set) var weapon: String
private(set) var health: Int
init(currentLocation: String, weapon: String, health: Int) {
self.currentLocation = currentLocation
self.weapon = weapon
self.health = health
}
func move(to location: String) {
currentLocation = location
}
func equip(weapon: String) {
self.weapon = weapon
}
func takeDamage(_ value: Int) {
health -= value
}
}The state is intentionally not publicly mutable. The object controls its own invariants.
Now we define a snapshot.
struct ProtagonistMemento {
fileprivate let currentLocation: String
fileprivate let weapon: String
fileprivate let health: Int
}The fields are hidden from the outside world. Only the originator can access them.
Next, the originator learns how to save and restore itself.
extension Protagonist {
func save() -> ProtagonistMemento {
ProtagonistMemento(
currentLocation: currentLocation,
weapon: weapon,
health: health
)
}
func restore(from memento: ProtagonistMemento) {
currentLocation = memento.currentLocation
weapon = memento.weapon
health = memento.health
}
}This is the key point of the pattern. Restoration logic stays inside the object. No external mutation is required.
Finally, a simple caretaker.
final class CheckpointStorage {
private var stack: [ProtagonistMemento] = []
func push(_ memento: ProtagonistMemento) {
stack.append(memento)
}
func pop() -> ProtagonistMemento? {
stack.popLast()
}
}Usage looks straightforward.
let protagonist = Protagonist(
currentLocation: "Castle",
weapon: "Sword",
health: 100
)
let storage = CheckpointStorage()
storage.push(protagonist.save())
protagonist.move(to: "Dungeon")
protagonist.equip(weapon: "Axe")
protagonist.takeDamage(30)
if let checkpoint = storage.pop() {
protagonist.restore(from: checkpoint)
}The object returns to its previous state without any external code knowing how that state is structured.
Why this design matters
I have seen many variations of Memento that technically work but break encapsulation. A common example involves exposing state through protocols or allowing external components to mutate the object directly. It feels flexible at first, then becomes fragile.
Keeping restoration inside the originator has a few practical advantages:
- State invariants remain protected
- The snapshot format can evolve without affecting external code
- Debugging becomes easier because state transitions are localized
Another detail worth calling out is immutability. A memento should represent a fixed point in time. Once created, it should not change.
Where it fits in iOS codebases
Memento works well in several real scenarios:
- Undo and redo stacks in editors
- Temporary form state with rollback on cancel
- Navigation flows where partial progress needs to be restored
- View models that need snapshot testing or state replay
In SwiftUI, this pattern often appears implicitly when state structs are copied and restored. In UIKit, it becomes more explicit, especially when working with complex coordinators or multi-step flows.
When not to use it
Not every piece of state deserves a snapshot. If the state is cheap to recompute or already derived from a single source of truth, adding Memento introduces unnecessary complexity.
I usually reach for it when reconstruction is either expensive or lossy. If rebuilding the state risks missing subtle details, capturing it directly is safer.
Undo-Redo
One of the most practical uses of Memento is undo and redo support. The idea is simple: before each change, the object saves its current state. Those snapshots are then stored in an undo stack. When the user triggers undo, the current state moves to a redo stack, and the last snapshot from the undo stack is restored.
Redo works in the opposite direction: the current state is saved back to the undo stack, then the latest snapshot from the redo stack is applied. One detail matters here: if the user makes a new change after undo, the redo history must be cleared, because the state timeline has branched. This keeps the behavior predictable and mirrors how undo and redo usually work in editors, forms, and other stateful interfaces.
final class Editor {
struct Memento {
fileprivate let text: String
}
private(set) var text: String = ""
func type(_ newText: String) {
text = newText
}
func save() -> Memento {
Memento(text: text)
}
func restore(from memento: Memento) {
text = memento.text
}
}
final class History {
private var undoStack: [Editor.Memento] = []
private var redoStack: [Editor.Memento] = []
func performChange(on editor: Editor, _ change: () -> Void) {
undoStack.append(editor.save())
redoStack.removeAll()
change()
}
func undo(on editor: Editor) {
guard let previous = undoStack.popLast() else { return }
redoStack.append(editor.save())
editor.restore(from: previous)
}
func redo(on editor: Editor) {
guard let next = redoStack.popLast() else { return }
undoStack.append(editor.save())
editor.restore(from: next)
}
}In this example, Editor plays the role of the originator. It knows how to save its current state into a Memento and how to restore itself from one later.
History acts as the caretaker. It does not know anything about how the editor stores its state internally. Its only responsibility is to keep snapshots in the correct order and move them between the undo and redo stacks.
That separation is exactly what makes Memento useful: state remains encapsulated inside the originator, while history management stays outside.
Closing thoughts
Memento looks simple on paper, but the implementation details matter. The pattern relies on a clear boundary: the originator owns its state, and snapshots remain opaque to the outside world. Once that boundary is respected, the pattern becomes a reliable tool rather than just another abstraction.
After analyzing the Memento pattern, we can discuss the next Behavioral Design Pattern, the Observer. See you in the following article.
Check out other posts in the Design Patterns series:
- Visitor Design Pattern in Swift
- Template Method Design Pattern in Swift
- Strategy Design Pattern in Swift
- State Design Pattern in Swift
- Observer Design Pattern in Swift
- Mediator Design Pattern in Swift
- Iterator Design Pattern in Swift
- Command Design Pattern in Swift
- Chain of Responsibility Design Pattern in Swift
- Proxy Design Pattern in Swift
- FlyWeight Design Pattern in Swift
- Facade Design Pattern in Swift
- Decorator Design Pattern in Swift
- Composite Design Pattern in Swift
- Bridge Design Pattern in Swift
- Adapter Design Pattern in Swift
- Singleton Design Pattern in Swift
- Prototype Design Pattern in Swift
- Builder Design Pattern in Swift
- Abstract Factory Design Pattern in Swift
- Factory Method Design Pattern in Swift
- Design Patterns: Basics
