Memento – Design Patterns in Swift

Memento is a behavioral design pattern that allows making snapshots of an object’s state and restoring it in the future. It doesn’t compromise the internal structure of the object it works with, as well as data kept inside the snapshots.

Memento Pattern – UML

Use Memento Pattern in Swift

The Originator holds some important states that may change over time. It also defines a method for saving the state inside a memento and another method for restoring the state from it.

import XCTest

class Originator {

    private var state: String

    init(state: String) {
        self.state = state
        print("Originator: My initial state is: \(state)")
    }

    func doSomething() {
        print("Originator: I'm doing something important.")
        state = generateRandomString()
        print("Originator: and my state has changed to: \(state)")
    }

    private func generateRandomString() -> String {
        return String(UUID().uuidString.suffix(4))
    }

    /// Saves the current state inside a memento.
    func save() -> Memento {
        return ConcreteMemento(state: state)
    }

    /// Restores the Originator's state from a memento object.
    func restore(memento: Memento) {
        guard let memento = memento as? ConcreteMemento else { return }
        self.state = memento.state
        print("Originator: My state has changed to: \(state)")
    }
}

For the sake of simplicity, the originator’s state is stored inside a single variable. The Originator’s business logic may affect its internal state. Therefore, the client should backup the state before launching methods of the business logic via the save() method.

The Memento interface provides a way to retrieve the memento’s metadata, such as creation date or name. However, it doesn’t expose the Originator’s state.

protocol Memento {

    var name: String { get }
    var date: Date { get }
}

The Concrete Memento contains the infrastructure for storing the Originator’s state.

class ConcreteMemento: Memento {

    /// The Originator uses this method when restoring its state.
    private(set) var state: String
    private(set) var date: Date

    init(state: String) {
        self.state = state
        self.date = Date()
    }

    /// The rest of the methods are used by the Caretaker to display metadata.
    var name: String { return state + " " + date.description.suffix(14).prefix(8) }
}

The Caretaker doesn’t depend on the Concrete Memento class. Therefore, it doesn’t have access to the originator’s state, stored inside the memento. It works with all mementos via the base Memento interface.

class Caretaker {

    private lazy var mementos = [Memento]()
    private var originator: Originator

    init(originator: Originator) {
        self.originator = originator
    }

    func backup() {
        print("\nCaretaker: Saving Originator's state...\n")
        mementos.append(originator.save())
    }

    func undo() {

        guard !mementos.isEmpty else { return }
        let removedMemento = mementos.removeLast()

        print("Caretaker: Restoring state to: " + removedMemento.name)
        originator.restore(memento: removedMemento)
    }

    func showHistory() {
        print("Caretaker: Here's the list of mementos:\n")
        mementos.forEach({ print($0.name) })
    }
}

Let’s see how it all works together.

class MementoConceptual: XCTestCase {

    func testMementoConceptual() {

        let originator = Originator(state: "Super-duper-super-puper-super.")
        let caretaker = Caretaker(originator: originator)

        caretaker.backup()
        originator.doSomething()

        caretaker.backup()
        originator.doSomething()

        caretaker.backup()
        originator.doSomething()

        print("\n")
        caretaker.showHistory()

        print("\nClient: Now, let's rollback!\n\n")
        caretaker.undo()

        print("\nClient: Once more!\n\n")
        caretaker.undo()
    }
}

Conclusion

In summary, Memento’s principle can be achieved using serialization, which is quite common in Swift. While it’s not the only and the most efficient way to make snapshots of an object’s state, it still allows storing state backups while protecting the originator’s structure from other objects.

Leave a Reply

Your email address will not be published. Required fields are marked *