Swift备忘录模式:对象状态的保存与恢复
痛点直击:状态管理的终极解决方案
你是否曾在开发中遇到以下困境?编辑文档时误操作导致内容丢失、游戏角色死亡后无法回到之前的存档状态、用户表单填写一半意外刷新页面——这些场景都指向同一个核心问题:如何安全地保存和恢复对象状态。传统的状态备份方式往往导致代码耦合严重、状态管理混乱,而备忘录模式(Memento Pattern)正是解决这一痛点的银弹方案。本文将深入剖析Swift中的备忘录模式实现,提供从基础概念到高级应用的完整指南,助你优雅处理对象状态管理难题。
核心概念:备忘录模式三要素
备忘录模式通过三个核心组件实现对象状态的安全管理,其UML类图如下:
1. 原发器(Originator)
- 职责:创建备忘录并记录当前内部状态,可使用备忘录恢复状态
- Swift实现:通常是需要状态管理的业务对象(如文档编辑器、游戏角色)
2. 备忘录(Memento)
- 职责:存储原发器的内部状态,对外部对象隐藏状态内容
- Swift实现:通常设计为不可变结构体,确保状态完整性
3. 负责人(Caretaker)
- 职责:管理备忘录的保存与获取,不操作或检查备忘录内容
- Swift实现:可实现为栈(支持撤销操作)、列表(支持历史记录)等数据结构
基础实现:简单文本编辑器
以下是一个完整的文本编辑器实现,展示备忘录模式的基础应用:
import Foundation
// MARK: - 备忘录
struct TextMemento {
let content: String
let cursorPosition: Int
let timestamp: Date
}
// MARK: - 原发器
class TextEditor {
private var content: String = ""
private var cursorPosition: Int = 0
// 创建备忘录
func createMemento() -> TextMemento {
TextMemento(
content: content,
cursorPosition: cursorPosition,
timestamp: Date()
)
}
// 从备忘录恢复
func restore(from memento: TextMemento) {
content = memento.content
cursorPosition = memento.cursorPosition
}
// 业务方法
func typeText(_ text: String) {
content.insert(contentsOf: text, at: String.Index(utf16Offset: cursorPosition, in: content))
cursorPosition += text.count
}
func moveCursor(to position: Int) {
cursorPosition = max(0, min(position, content.count))
}
// 状态展示
var currentStateDescription: String {
return "Content: '\(content)', Cursor: \(cursorPosition)"
}
}
// MARK: - 负责人
class HistoryManager {
private var mementos: [TextMemento] = []
private weak var editor: TextEditor?
init(editor: TextEditor) {
self.editor = editor
}
func saveState() {
guard let memento = editor?.createMemento() else { return }
mementos.append(memento)
print("Saved state \(mementos.count) at \(memento.timestamp.formatted())")
}
func undo() -> Bool {
guard mementos.count > 0, let editor = editor else { return false }
let lastMemento = mementos.removeLast()
editor.restore(from: lastMemento)
print("Restored state from \(lastMemento.timestamp.formatted())")
return true
}
func getHistoryCount() -> Int {
return mementos.count
}
}
// MARK: - 使用示例
let editor = TextEditor()
let history = HistoryManager(editor: editor)
editor.typeText("Hello ")
history.saveState() // 保存状态1
editor.typeText("World!")
history.saveState() // 保存状态2
print("Current state: \(editor.currentStateDescription)")
// 输出: Current state: 'Hello World!', Cursor: 12
history.undo()
print("After undo: \(editor.currentStateDescription)")
// 输出: After undo: 'Hello ', Cursor: 6
高级应用:泛型备忘录与状态栈
泛型备忘录设计
为支持多种类型的状态存储,可设计泛型备忘录:
// 泛型备忘录协议
protocol MementoType {
associatedtype State
var state: State { get }
var timestamp: Date { get }
}
// 泛型备忘录实现
struct GenericMemento<State: Codable>: MementoType {
let state: State
let timestamp: Date
}
// 泛型原发器协议
protocol OriginatorType {
associatedtype Memento: MementoType
func createMemento() -> Memento
func restore(from memento: Memento)
}
带版本控制的负责人
实现支持版本命名和状态比较的高级负责人:
class VersionedCaretaker<State: Codable & Equatable> {
private struct VersionedMemento: MementoType {
let state: State
let timestamp: Date
let versionName: String
let versionNumber: Int
}
private var mementos: [VersionedMemento] = []
private var nextVersionNumber = 1
func saveState(_ state: State, versionName: String? = nil) {
// 避免保存重复状态
guard mementos.last?.state != state else { return }
let versionName = versionName ?? "Auto-Save \(nextVersionNumber)"
let memento = VersionedMemento(
state: state,
timestamp: Date(),
versionName: versionName,
versionNumber: nextVersionNumber
)
mementos.append(memento)
nextVersionNumber += 1
}
func restoreVersion(versionNumber: Int) -> State? {
guard let memento = mementos.first(where: { $0.versionNumber == versionNumber }) else {
return nil
}
return memento.state
}
func listVersions() -> [(version: Int, name: String, date: Date)] {
return mementos.map { ($0.versionNumber, $0.versionName, $0.timestamp) }
}
}
持久化存储:备忘录的本地保存
结合Swift的Codable协议实现备忘录的本地持久化:
class PersistentCaretaker {
private let fileManager = FileManager.default
private let baseURL: URL
init() {
// 获取文档目录
guard let url = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else {
fatalError("无法获取文档目录")
}
self.baseURL = url.appendingPathComponent("mementos")
// 创建目录
if !fileManager.fileExists(atPath: baseURL.path) {
try? fileManager.createDirectory(at: baseURL, withIntermediateDirectories: true)
}
}
// 保存备忘录到本地
func saveToDisk<T: Codable>(memento: T, id: String) -> Bool {
let url = baseURL.appendingPathComponent("\(id).json")
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
do {
let data = try encoder.encode(memento)
try data.write(to: url)
return true
} catch {
print("保存失败: \(error.localizedDescription)")
return false
}
}
// 从本地加载备忘录
func loadFromDisk<T: Codable>(id: String) -> T? {
let url = baseURL.appendingPathComponent("\(id).json")
do {
let data = try Data(contentsOf: url)
return try JSONDecoder().decode(T.self, from: data)
} catch {
print("加载失败: \(error.localizedDescription)")
return nil
}
}
}
线程安全:并发环境下的备忘录管理
在多线程环境下使用备忘录模式,需添加线程安全保障:
class ThreadSafeCaretaker<State> {
private var mementos: [State] = []
private let queue = DispatchQueue(label: "com.example.MementoQueue", attributes: .concurrent)
// 并发读取
func getMementoCount() -> Int {
return queue.sync { mementos.count }
}
// 同步写入
func addMemento(_ memento: State) {
queue.async(flags: .barrier) { [weak self] in
self?.mementos.append(memento)
}
}
// 同步修改
func popMemento() -> State? {
return queue.sync(flags: .barrier) {
return mementos.isEmpty ? nil : mementos.removeLast()
}
}
}
应用场景与最佳实践
典型应用场景
| 场景 | 备忘录实现要点 | Swift技术选型 |
|---|---|---|
| 文本编辑器撤销功能 | 存储文本内容、光标位置、选区 | 结构体+数组栈 |
| 游戏存档系统 | 存储角色状态、场景数据、进度信息 | Codable+文件存储 |
| 配置管理 | 保存配置项集合、版本信息 | 字典+UserDefaults |
| 事务回滚 | 存储操作前状态、依赖关系 | 不可变结构体+链表 |
| 状态历史记录 | 带时间戳的状态序列 | 数组+时间戳索引 |
性能优化策略
- 增量备忘录:仅存储与前一状态的差异部分
struct DeltaMemento: Codable {
let baseVersion: Int
let changes: [String: AnyCodable] // 仅存储变更的字段
}
- 状态压缩:对大型状态数据进行压缩存储
func compressMemento(data: Data) -> Data? {
guard let compressed = try? (data as NSData).compressed(using: .zlib) else {
return nil
}
return compressed as Data
}
- LRU缓存:限制备忘录数量,淘汰最久未使用的状态
class LRUCacheCaretaker<State> {
private let capacity: Int
private var cache: [String: State] = [:]
private var usageOrder: [String] = []
init(capacity: Int) {
self.capacity = capacity
}
// 实现LRU逻辑...
}
备忘录模式与Swift特性结合
1. 不可变状态设计
利用Swift值类型特性确保状态不变性:
// 不可变游戏状态
struct GameState: Codable {
let playerPosition: (x: Int, y: Int)
let score: Int
let health: Double
let inventory: [Item]
// 状态变更返回新实例
func with(newScore: Int) -> GameState {
return GameState(
playerPosition: playerPosition,
score: newScore,
health: health,
inventory: inventory
)
}
}
2. 属性包装器实现状态追踪
@propertyWrapper
struct MementoTracked<Value: Codable> {
private var value: Value
private var history: [Value] = []
init(wrappedValue: Value) {
self.value = wrappedValue
}
var wrappedValue: Value {
get { value }
set {
history.append(value) // 保存旧值
value = newValue
}
}
// 回滚功能
mutating func rollback() {
guard !history.isEmpty else { return }
value = history.removeLast()
}
}
// 使用属性包装器
class SettingsManager {
@MementoTracked var volume: Float = 0.5
@MementoTracked var brightness: Float = 1.0
}
常见问题与解决方案
循环引用问题
问题:如果备忘录中包含引用类型,可能导致循环引用
解决方案:使用弱引用或设计为值类型
// 错误示例:可能导致循环引用
class BadMemento {
let originator: Originator // 强引用
// ...
}
// 正确示例:使用值类型
struct GoodMemento {
let state: Originator.State // 仅存储值类型状态
// ...
}
状态一致性问题
问题:恢复状态时可能与当前系统状态冲突
解决方案:添加版本校验和依赖检查
func restoreWithValidation(from memento: Memento) throws {
guard memento.systemVersion == currentSystemVersion else {
throw MementoError.incompatibleVersion
}
guard memento.dependencies == currentDependencies else {
throw MementoError.dependencyConflict
}
// 执行恢复操作
}
总结与扩展学习
备忘录模式为Swift应用提供了优雅的状态管理方案,其核心价值在于:
- 职责分离:将状态管理逻辑与业务逻辑解耦
- 状态安全:确保状态的完整性和一致性
- 操作可逆:支持灵活的撤销和恢复功能
相关设计模式对比
扩展学习资源
-
Swift标准库中的备忘录思想:
Array的withUnsafeBytes临时状态访问NSUndoManager的实现原理
-
高级备忘录实现:
- 结合Combine框架实现响应式状态管理
- 使用SwiftUI的
@StateObject和ObservableObject实现视图状态恢复
通过本文的学习,你已掌握Swift中备忘录模式的设计思想和实现技巧。无论是简单的撤销功能还是复杂的状态管理系统,备忘录模式都能帮助你编写更健壮、更灵活的代码。记住,优秀的状态管理是高质量应用的基石,而备忘录模式正是构建这一基石的关键工具。
收藏与分享
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



