iOS Design Patterns: Memento pattern

Ali Jaber
3 min readApr 14, 2021

Memento Design patterns is from the behavioral design pattern family, which allows objects to be cached(saved) inside the app and restored for any further use.

Memento pattern consists of three parts:

  • Originator: which is the object which has been saved or restored.
  • Memento: represents the stored state of the object.
  • Caretaker: its the operator, it asks for a save from the originator, and in response, it gets the memento response.

Usage:

This pattern is useful whenever you want to save some data, and restore later.

For example, this pattern implementation can be useful if you want to save a gaming system inside your app, where the originator is the game state(level, score, remainingLives, etc), the memento is the saved data, and the caretaker is the Game system.

Requirements:

iOS apps use an Encoder to encode the originators state into memento, and a decoder to decode a memento back to an originator.

Don’t forget to use Codable for the types you want to save and restore, which was introduced by Apple in Swift 4, any object or type that conforms to Codable “convert itself into and out of an external representation”. In other words, its a type that can restore and save itself, you will get familiar with it in the following example:

import Foundationpublic class MyGame: Codable {public class GameState: Codable {public var remainingLives: Int = 3public var reachedLevel: Int = 1public var userScore: Int = 0}public var gameState = GameState()public func addRandomPoints() {gameState.userScore += 1975}public func killedByMonster() {gameState.remainingLives -= 1}}public class MyGameSystem {private let decoder = JSONDecoder()private let encoder = JSONEncoder()private let userDefaults = UserDefaults.standard
public func saveGame(_ currentGame: MyGame, gameTitle: String) throws { // this function handles the data encoding and save it on the disk using the userDefaults instancelet encodedGameData = try encoder.encode(currentGame)userDefaults.setValue(encodedGameData, forKey: gameTitle)}
public func loadGame(gameTitle: String) throws -> MyGame {
// this function handles the data decoding it to its initial object
guard let data = userDefaults.data(forKey: gameTitle), let decodedGame = try? decoder.decode(MyGame.self, from: data) else { throw Error.gameNotAvailable }return decodedGame}public enum Error: String, Swift.Error {case gameNotAvailable}}var myGame = MyGame() // create a new game objectmyGame.killedByMonsterr()myGame.addRandomPoints()let system = MyGameSystem()try system.saveGame(myGame, gameTitle: "i was playing this incredible game") // saving the objectmyGame = MyGame()// new game instance is createdprint("New Game Score: \(myGame.gameState.userScore)") // value should be 0 here, since its getting it from the new objectgame = try! system.loadGame(gameTitle: "i was playing this incredible game") // retrieving the saved objectprint("My previous game Score was : \(game.state.score)") /// getting the saved value which is 1975

Decoder was used to decode the object from data, encoder was used to encode the object to data, and userDefaults to handle the data save on the disk. Data will still be available even if the user logged out, or the app was relaunched.

Try was used because encoding and decoding might throw an errors, so be careful in using try!(force unwrapped), since if any required data is missing, the app will crash.

--

--

Ali Jaber

iOS Engineer with more than 3 years experience in developing mobile apps in both swift and objective C for both local and international customers