ソフトウェア設計

Swiftのメメントパターン完全ガイド(取り消し実装例)

iOSアプリに「取り消し」ボタンを追加していて、行き詰まったことはありませんか?

読了 5 分
Swiftのメメントパターン完全ガイド(取り消し実装例)のカバー画像

iOSアプリに「取り消し」ボタンを追加していて、行き詰まったことはありませんか?

テキストエディタで直前の編集を取り消せるようにするには、まず以前の状態をどう保持するかを考える必要があります。

オブジェクト内部のプロパティをあちこちでバックアップしていると、コードはすぐに煩雑になります。

そんなときにぴったりなのがSwiftのメメントパターンです。今日は、このパターンでオブジェクトの状態を上手に保存・復元する方法を、実際に使った経験とコードを交えて紹介します。

メメントパターンは、カプセル化を壊さずにオブジェクトの内部状態を外部へ保存し、後からその状態へ戻すデザインパターンです。

一言でいえば「スナップショットを撮っておく」ことです。取り消し(Undo)、ゲームのセーブ、フォームの一時保存などの基盤になります。


メメントパターンとは?まずは3つの役割から 🧩

メメントパターンの登場人物は3つだけです。ここを理解すれば、半分は終わったようなものです。

  • Originator(元のオブジェクト):状態を持つ主人公です。自分の状態をメメントに保存し、メメントを受け取って復元します。
  • Memento(メメント):特定時点の状態を収めたスナップショットです。一度作成したら内容が変わらないのが理想です。
  • Caretaker(管理者):メメントを保管する役割です。通常は取り消しスタックが該当します。

重要なのは、管理者がメメントの「中身」を見ないことです。

管理者はスナップショットを受け取って積み重ね、必要なときに渡し返すだけです。元のオブジェクトの内部構造を知っているのは元のオブジェクト自身だけです。

これによりカプセル化が保たれます。ここがメメントパターン最大の魅力です。


Swiftでメメントパターンを実装するには?

簡単なテキストエディタを例にします。まずメメントと元のオブジェクトを定義します。

以下は、状態を保持するメメントと、それを作成・復元する元のオブジェクトです。

// 状態スナップショット — 作成後に変更されないように let
struct EditorMemento {
    let text: String
}

final class TextEditor {
    var text: String = ""
    func save() -> EditorMemento { EditorMemento(text: text) }   // 保存
    func restore(_ memento: EditorMemento) { text = memento.text } // 復元
}

save()は現在の状態をスナップショットとして取得し、restore(_:)は受け取ったスナップショットへ戻します。

ポイントは、EditorMementoのプロパティをletにしたことです。保存時点の状態が後から壊れないようにしています。

saveとrestoreを分けるだけで、コードはこれほどすっきりします
saveとrestoreを分けるだけで、コードはこれほどすっきりします

実践:取り消し(Undo)機能を作る ⏪

次に管理者、つまり取り消しスタックを追加します。先ほど作ったメメントを順に積み重ねる役割です。

final class UndoManager {
    private var history: [EditorMemento] = []
    func backup(_ memento: EditorMemento) { history.append(memento) }
    func undo() -> EditorMemento? { history.popLast() } // 最新の状態を取り出す
}

実際の流れは次のようになります。

保存して元に戻す流れを図にすると、このようになります
保存して元に戻す流れを図にすると、このようになります

ユーザーが文章を編集する直前に、backupで現在の状態を保存しておきます。

「元に戻す」を押すと、undoが最後のスナップショットを取り出し、元のオブジェクトがそれでrestoreします。

実際に組み込んでみると、編集ロジックと保存ロジックがきれいに分かれ、コードがずっと読みやすくなりました。

文字の追加・削除・書式変更など複数の編集が混ざっても、保存方法を1つに統一できるので、保守も楽でした。

1つの元に戻すボタンが、このパターンの上で動いています
1つの元に戻すボタンが、このパターンの上で動いています

Swiftらしく使うコツ:Codableとstructの活用

メメントパターンは、Javaのような言語を前提に説明されることが多いです。しかしSwiftでは、言語の特性を活かすとずっと扱いやすくなります。

1つ目は、メメントにはclassよりstructが向いていることです。値型なので、コピーした瞬間に独立したスナップショットになります。参照が絡んで元の状態が変わる心配も減ります。

2つ目は、メメントにCodableを採用すると、状態をファイルやUserDefaultsに保存しやすくなることです。アプリを再起動しても維持されるセーブ機能へ拡張できます。

3つ目は、UIKitを使うなら、すでにUndoManagerというシステムクラスがあるので、まずそれを確認することです。自作する前に標準ツールを知るのが先です。


よくある質問(Q&A)

Q. 変数にバックアップしておくだけではだめですか?

簡単なケースなら問題ありません。ただ、状態が複数あったり保存・復元ロジックが複雑になったりすると、散在したバックアップコードがバグの温床になります。メメントパターンなら1か所にまとめられます。

Q. メモリを使いすぎませんか?

その通りです。スナップショットを無制限に積むとメモリ負荷が増えます。そのため実務では、履歴を20〜50件程度に制限したり、変更部分だけを保存したりして最適化します。


メメントパターンは名前こそ unfamiliarですが、実際には「スナップショットを撮って元に戻す」という単純なアイデアです。

取り消しや一時保存機能を検討しているなら、今日のサンプルコードを実際に入力しながら試してみてください。カプセル化を守りながら状態を扱う感覚が、きっとつかめます。応援しています! 😊

あわせて読みたい