Chapter 7: Memento Pattern
大綱
When should you use it?
The memento pattern allows an object to be saved and restored.
The originator is the object to be saved or restored.
The memento represents a stored state.
The caretaker requests a save from the originator and receives a memento in response.
The caretaker is responsible for persisting the memento and, later on, providing the memento back to the originator to restore the originator’s state.

When should you use it?
Use the memento pattern whenever you want to save and later restore an object’s state.
Playground example
目標: use this pattern to implement a save game system, where the originator is the game state (such as level, health, number of lives, etc),
the memento is saved data
the caretaker is the gaming system.
// MARK: - Example
var game = Game()
game.monstersEatPlayer()
game.rackUpMassivePoints()
// Save Game
let gameSystem = GameSystem()
try gameSystem.save(game, title: "Best Game Ever")
// New Game
game = Game()
print("New Game Score: \(game.state.score)") // New Game Score: 0
// Load Game
game = try! gameSystem.load(title: "Best Game Ever")
print("Loaded Game Score: \(game.state.score)") // Loaded Game Score: 9002
Originator
// MARK: - Originator
// has an internal State that holds onto game properties, and it has methods to handle in-game actions. You also declare Game and State conform to Codable.
public class Game: Codable {
public class State: Codable {
public var attemptsRemaining: Int = 3
public var level: Int = 1
public var score: Int = 0
}
public var state = State()
public func rackUpMassivePoints() {
state.score += 9002
}
public func monstersEatPlayer() {
state.attemptsRemaining -= 1
}
}
Memento
// MARK: - Memento
// Technically, you don’t need to declare this line at all. Rather, it’s here to inform you the GameMemento is actually Data
typealias GameMemento = Data
CareTaker
// MARK: - CareTaker
public class GameSystem {
// use decoder to decode Games from Data, encoder to encode Games to Data, and userDefaults to persist Data to disk
private let decoder = JSONDecoder()
private let encoder = JSONEncoder()
private let userDefaults = UserDefaults.standard
// use encoder to encode the passed-in game. This operation may throw an error, so you must prefix it with try. You then save the resulting data under the given title within userDefaults.
public func save(_ game: Game, title: String) throws {
let data = try encoder.encode(game)
userDefaults.set(data, forKey: title)
}
// get data from userDefaults for the given title. You then use decoder to decode the Game from the data. If either operation fails, you throw a custom error for Error.gameNotFound.
public func load(title: String) throws -> Game {
guard let data = userDefaults.data(forKey: title),
let game = try? decoder.decode(Game.self, from: data)
else {
throw Error.gameNotFound
}
return game
}
public enum Error: String, Swift.Error {
case gameNotFound
}
}
What should you be careful about?
Be careful when adding or removing Codable properties: both encoding and decoding can throw an error. If you force unwrap these calls using try! and you’re missing any required data, your app will crash!
To mitigate this problem, avoid using try! unless you’re absolutely sure the operation will succeed. You should also plan ahead when changing your models.
carefully consider how to handle version upgrades.
You might choose to delete old data whenever you encounter a new version, create an upgrade path to convert from old to new data, or even use a combination of these approaches.
Tutorial project
Key points
The memento pattern allows an object to be saved and restored. It involves three types: the originator, memento and caretaker.
The originator is the object to be saved; the memento is a saved state; and the caretaker handles, persists and retrieves mementos.
iOS provides Encoder for encoding a memento to, and Decoder for decoding from, a memento. This allows encoding and decoding logic to be used across originators.
Last updated
Was this helpful?