import Foundation // MARK: - 持久化协议 /// 阅读器持久化协议 /// 定义了阅读位置、书签、高亮和用户设置的存储接口 /// 调用方可实现此协议以对接自定义存储(如数据库、云端同步等) public protocol RDEPUBReaderPersistence: AnyObject { /// 加载指定书籍的上次阅读位置 /// - Parameter bookIdentifier: 书籍唯一标识符 /// - Returns: 保存的阅读位置,nil 表示首次打开 func loadLocation(for bookIdentifier: String) -> RDEPUBLocation? /// 保存指定书籍的当前阅读位置 /// - Parameters: /// - location: 阅读位置 /// - bookIdentifier: 书籍唯一标识符 func saveLocation(_ location: RDEPUBLocation, for bookIdentifier: String) /// 加载指定书籍的所有书签 func loadBookmarks(for bookIdentifier: String) -> [RDEPUBBookmark] /// 保存指定书籍的所有书签 func saveBookmarks(_ bookmarks: [RDEPUBBookmark], for bookIdentifier: String) /// 加载指定书籍的所有高亮标注 func loadHighlights(for bookIdentifier: String) -> [RDEPUBHighlight] /// 保存指定书籍的所有高亮标注 func saveHighlights(_ highlights: [RDEPUBHighlight], for bookIdentifier: String) /// 加载全局阅读器用户设置(字号、行距、主题等) func loadReaderSettings() -> RDEPUBReaderSettings? /// 保存全局阅读器用户设置 func saveReaderSettings(_ settings: RDEPUBReaderSettings) } // MARK: - 默认实现 /// 协议的默认空实现,书签/高亮/设置为可选功能 /// DEBUG 模式下会对 no-op 行为输出警告,便于发现未对接持久化层的误用 public extension RDEPUBReaderPersistence { func loadBookmarks(for bookIdentifier: String) -> [RDEPUBBookmark] { #if DEBUG print("[RDEPUBReaderPersistence] ⚠️ loadBookmarks called on default no-op implementation for: \(bookIdentifier)") #endif return [] } func saveBookmarks(_ bookmarks: [RDEPUBBookmark], for bookIdentifier: String) { #if DEBUG print("[RDEPUBReaderPersistence] ⚠️ saveBookmarks(\(bookmarks.count) items) called on default no-op implementation for: \(bookIdentifier)") #endif } func loadReaderSettings() -> RDEPUBReaderSettings? { #if DEBUG print("[RDEPUBReaderPersistence] ⚠️ loadReaderSettings called on default no-op implementation") #endif return nil } func saveReaderSettings(_ settings: RDEPUBReaderSettings) { #if DEBUG print("[RDEPUBReaderPersistence] ⚠️ saveReaderSettings called on default no-op implementation") #endif } } // MARK: - UserDefaults 持久化实现 /// 基于 UserDefaults 的持久化实现 /// 数据以 JSON 格式存储,按书籍标识符分隔存储位置 /// 适合轻量级场景,大量数据建议改用 SQLite 或文件系统 public final class RDEPUBUserDefaultsPersistence: RDEPUBReaderPersistence { /// UserDefaults 实例 private let defaults: UserDefaults /// 阅读位置的 key 前缀 private let locationPrefix: String /// 书签列表的 key 前缀 private let bookmarksPrefix: String /// 高亮列表的 key 前缀 private let highlightsPrefix: String /// 用户设置的 key private let settingsKey: String /// 初始化 UserDefaults 持久化策略 /// - Parameters: /// - defaults: UserDefaults 实例,默认 .standard /// - locationPrefix: 阅读位置 key 前缀 /// - bookmarksPrefix: 书签 key 前缀 /// - highlightsPrefix: 高亮 key 前缀 /// - settingsKey: 用户设置 key public init( defaults: UserDefaults = .standard, locationPrefix: String = "ssreader.epub.location.", bookmarksPrefix: String = "ssreader.epub.bookmarks.", highlightsPrefix: String = "ssreader.epub.highlights.", settingsKey: String = "ssreader.epub.settings" ) { self.defaults = defaults self.locationPrefix = locationPrefix self.bookmarksPrefix = bookmarksPrefix self.highlightsPrefix = highlightsPrefix self.settingsKey = settingsKey } public func loadLocation(for bookIdentifier: String) -> RDEPUBLocation? { guard let data = defaults.data(forKey: locationPrefix + bookIdentifier) else { return nil } return try? JSONDecoder().decode(RDEPUBLocation.self, from: data) } public func saveLocation(_ location: RDEPUBLocation, for bookIdentifier: String) { guard let data = try? JSONEncoder().encode(location) else { return } defaults.set(data, forKey: locationPrefix + bookIdentifier) } public func loadBookmarks(for bookIdentifier: String) -> [RDEPUBBookmark] { guard let data = defaults.data(forKey: bookmarksPrefix + bookIdentifier) else { return [] } return (try? JSONDecoder().decode([RDEPUBBookmark].self, from: data)) ?? [] } public func saveBookmarks(_ bookmarks: [RDEPUBBookmark], for bookIdentifier: String) { guard let data = try? JSONEncoder().encode(bookmarks) else { return } defaults.set(data, forKey: bookmarksPrefix + bookIdentifier) } public func loadHighlights(for bookIdentifier: String) -> [RDEPUBHighlight] { guard let data = defaults.data(forKey: highlightsPrefix + bookIdentifier) else { return [] } return (try? JSONDecoder().decode([RDEPUBHighlight].self, from: data)) ?? [] } public func saveHighlights(_ highlights: [RDEPUBHighlight], for bookIdentifier: String) { guard let data = try? JSONEncoder().encode(highlights) else { return } if data.count > 1_048_576 { #if DEBUG print("[RDEPUBUserDefaultsPersistence] ⚠️ saveHighlights data size (\(data.count) bytes) exceeds 1MB for: \(bookIdentifier)") #endif } defaults.set(data, forKey: highlightsPrefix + bookIdentifier) } public func loadReaderSettings() -> RDEPUBReaderSettings? { guard let data = defaults.data(forKey: settingsKey) else { return nil } return try? JSONDecoder().decode(RDEPUBReaderSettings.self, from: data) } public func saveReaderSettings(_ settings: RDEPUBReaderSettings) { guard let data = try? JSONEncoder().encode(settings) else { return } defaults.set(data, forKey: settingsKey) } }