# ReadViewSDK 系统架构文档 > 最后更新:2026-06-09 --- ## 1. 项目概览 ReadViewSDK 是一个 iOS EPUB 阅读器 SDK,支持文本重排(Reflowable)和固定布局(Fixed Layout)两种 EPUB 格式,同时兼容纯文本 (.txt) 文件。SDK 提供完整的 EPUB 解析、文本渲染、分页计算、阅读 UI 和标注管理能力。 **技术栈:** - 语言:Swift 5,最低 iOS 15.6 - 依赖:DTCoreText(HTML→NSAttributedString)、ZIPFoundation(EPUB 解压)、SnapKit(Auto Layout)、SSAlertSwift(弹窗) - 构建:CocoaPods,本地 pod 引用 --- ## 2. 分层架构 ``` ┌─────────────────────────────────────────────────────────────┐ │ ReadViewDemo (Demo App) │ │ ViewController · LaunchAutomationPlan · UITests │ └──────────────────────────────┬──────────────────────────────┘ │ imports RDReaderView ┌──────────────────────────────▼──────────────────────────────┐ │ EPUBUI 层 │ │ RDEPUBReaderController · Coordinators · Settings · TextPage │ │ ChapterRuntime(Loader · Store · DiskCache · PageMap) │ └──────────────────────────────┬──────────────────────────────┘ │ ┌──────────────────────────────▼──────────────────────────────┐ │ RDReaderView 层 │ │ RDReaderView · FlowLayout · PreloadController │ │ SpreadResolver · TapRegionHandler · PagingController │ └──────────────────────────────┬──────────────────────────────┘ │ ┌──────────────────────────────▼──────────────────────────────┐ │ EPUBTextRendering 层 │ │ TypesetterPipeline · DTCoreTextRenderer · Pagination │ │ BuildPipeline(TextBookBuilder · Cache · Sampler) │ └──────────────────────────────┬──────────────────────────────┘ │ ┌──────────────────────────────▼──────────────────────────────┐ │ EPUBCore 层 │ │ RDEPUBParser · Publication · ResourceResolver │ │ ReadingSession · WebView · JavaScriptBridge · Paginator │ └─────────────────────────────────────────────────────────────┘ ``` ### 各层职责 | 层 | 职责 | 核心类 | |---|---|---| | **EPUBCore** | EPUB 文件解析、资源管理、WebView 渲染、JS 桥接 | `RDEPUBParser`, `RDEPUBPublication`, `RDEPUBWebView` | | **EPUBTextRendering** | HTML→NSAttributedString 转换、排版、分页、全书构建 | `RDEPUBTextBookBuilder`, `RDEPUBCoreTextPageFrameFactory` | | **RDReaderView** | 通用翻页容器、手势识别、页面预加载、双页布局 | `RDReaderView`, `RDReaderFlowLayout`, `RDReaderPreloadController` | | **EPUBUI** | 阅读器控制器、协调器模式、设置面板、按需加载、磁盘缓存 | `RDEPUBReaderController`, `RDEPUBReaderPaginationCoordinator` | --- ## 3. 核心数据流 ### 3.1 打开书籍流程 ``` 用户点击书籍 │ ▼ RDEPUBReaderController(epubURL:delegate:persistence:) // 初始化,传入 epubURL │ ├─ viewDidLoad() → startInitialLoadIfNeeded() │ │ │ ├─ RDEPUBReaderLoadCoordinator.startInitialLoadIfNeeded() │ │ ├─ RDEPUBParser.parse(epubURL:) // 解压 ZIP → 解析 OPF → 构建 spine/TOC │ │ │ └─ extractArchiveIfNeeded() // ZIPFoundation 解压到 Caches │ │ │ └─ parseContainerRootFile() // SAX 解析 container.xml │ │ │ └─ parseOPF() // SAX 解析 OPF (metadata/manifest/spine) │ │ │ └─ parseTOC() // NCX 或 Navigation Document │ │ │ │ │ ├─ RDEPUBPublication(parser:) // 创建门面对象 │ │ ├─ 恢复持久化数据(书签/高亮/位置) │ │ └─ applyParsedPublication() │ │ │ └─ 判断 readingProfile: │ ├─ .textReflowable → 文本排版路径(本 SDK 核心路径) │ ├─ .webInteractive → WebView 渲染路径 │ └─ .webFixedLayout → 固定布局路径 │ └─ runtime.paginatePublication() // 进入分页流程 ``` ### 3.2 文本重排分页流程(大书优化路径) ``` paginateTextPublication() │ ├─ restoreBookPageMapIfPossible() // 尝试从磁盘恢复完整 BookPageMap │ └─ summaryDiskCache.readAll(keys:) // 批量读取磁盘摘要 │ └─ 如果完整 → 直接 applyBookPageMap() → 完成 │ ├─ 快速打开路径: │ ├─ loadFirstRenderableRuntimeChapter() // 加载第一个可渲染章节 │ ├─ loadInitialRuntimeChapters() // 加载窗口内相邻章节 │ ├─ makePartialPageMap() // 构建局部 BookPageMap │ └─ applyBookPageMap() // 应用到 UI,用户可立即阅读 │ └─ paginateMetadataOnly() // 后台元数据解析 │ ├─ 预计算 contentHash(串行) // 读取所有章节 HTML + SHA-256 ├─ readAll(keys:) 恢复已有缓存 ├─ waitForReadingInteractionToSettle() // 等待用户操作冷却 0.8s │ ├─ OperationQueue (并发 N): │ ├─ buildChapter() // 渲染单章 (HTML→NSAttrStr→分页) │ ├─ chapterCacheKey() // 构建缓存键(复用预计算 hash) │ ├─ RDEPUBChapterSummary 写盘 // 异步写入磁盘摘要 │ └─ 每 32 章刷新 BookPageMap // 增量更新 UI │ └─ 最终 applyBookPageMap() // 完整页码映射 ``` ### 3.3 单章渲染管线 ``` Raw HTML (从 EPUB 解压目录读取) │ ▼ [RDEPUBTextTypesetterPipeline] ├─ RDEPUBHTMLNormalizer // 去 CR、合并空行、规范化附件标记 ├─ RDEPUBSemanticMarkerInjector // 注入分页语义标记 ${rd-sem-start/end} ├─ RDEPUBRenderDiagnosticsCollector // 内联 、收集图片诊断 ├─ RDEPUBStyleSheetComposer // 构建 5 层 CSS (default/replace/dark/epub/user) ├─ RDEPUBFontNormalizer // 注册 @font-face 嵌入字体 ├─ RDEPUBFragmentMarkerInjector // 注入 fragment 锚点标记 ${id=xxx} │ ▼ [RDEPUBDTCoreTextRenderer] ├─ DTHTMLAttributedStringBuilder // HTML → NSAttributedString ├─ applyPaginationSemantics() // 将语义标记转为NSAttributedString属性 ├─ extractFragmentOffsets() // 提取 fragment ID→offset 映射 ├─ normalizeReadingAttributes() // 规范化字体、行距、颜色、附件 │ ▼ [RDEPUBChapterPageCounter] ├─ CTFramesetter 创建帧 ├─ RDEPUBPageBreakPolicy 应用语义边界规则 │ ├─ avoidPageBreakInside // 避免块内分页 │ ├─ keepWithNext // 标题与正文不分离 │ ├─ widow/orphan 控制 // 孤行/寡行控制 │ └─ pageRelate // 微信读书跨页关联 ├─ RDEPUBChapterTailNormalizer // 删除/合并空白尾页 │ ▼ [RDEPUBTextBookBuilder] └─ 组装 RDEPUBTextPage[] → RDEPUBTextChapter → RDEPUBTextBook ``` --- ## 4. 缓存架构(三级缓存) ``` ┌─────────────────────────────────────────────────────────┐ │ Tier 1: 内存缓存 (RDEPUBChapterRuntimeStore) │ │ ├─ chapterDataCache: [Int: RDEPUBRuntimeChapter] │ │ ├─ pageCountCache: [CacheKey: RDEPUBRuntimePageCount] │ │ ├─ imageCache: NSCache (50 上限) │ │ └─ 窗口驱逐:只保留当前章节 ± windowRadius 的章节 │ └──────────────────────────┬──────────────────────────────┘ │ miss ┌──────────────────────────▼──────────────────────────────┐ │ Tier 2: 磁盘摘要缓存 (RDEPUBChapterSummaryDiskCache) │ │ ├─ 路径: ~/Caches/RDEPUBChapterSummaryCache/{bookID}/ │ │ ├─ 文件名: SHA256(bookID_spineIdx_renderSig_contentHash)│ │ ├─ 格式: JSON (RDEPUBChapterSummary) │ │ ├─ 写入: 异步(serial DispatchQueue) │ │ └─ 读取: 同步(readAll 批量读取) │ └──────────────────────────┬──────────────────────────────┘ │ miss ┌──────────────────────────▼──────────────────────────────┐ │ Tier 3: 全书分页缓存 (RDEPUBTextBookCache) │ │ ├─ 路径: ~/Caches/RDEPUBTextBookCache/ │ │ ├─ 格式: NSSecureCoding archive │ │ ├─ 内容: 每章的 pageRanges + breakReasons + semanticHints│ │ └─ 失效: schemaVersion 变更时全部失效 │ └─────────────────────────────────────────────────────────┘ ``` ### 缓存键设计 ```swift struct RDEPUBChapterCacheKey: Hashable { let bookID: String // 书籍唯一标识 let spineIndex: Int // 章节索引 let renderSignature: String // 渲染参数签名(字体/字号/行距/布局/版本) let chapterContentHash: String // 章节 HTML 的 SHA-256 } ``` 缓存键的四元组设计确保: - 换字体/字号 → renderSignature 变化 → 缓存失效 - EPUB 内容更新 → contentHash 变化 → 缓存失效 - 不同书籍 → bookID 不同 → 互不干扰 --- ## 5. 章节按需加载架构 ``` RDEPUBReaderController │ ├─ RDEPUBReaderContext // 共享状态中心 │ ├─ parser, publication, readingSession │ ├─ configuration, persistence │ └─ 便捷方法 (renderStyle, layoutConfig, cacheKey) │ ├─ RDEPUBReaderRuntime // 运行时协调器集合(Facade 模式) │ ├─ chapterLoader // 章节加载器 │ ├─ chapterRuntimeStore // 内存缓存 │ ├─ summaryDiskCache // 磁盘摘要缓存 │ ├─ pageResolver // 页码解析器 │ ├─ loadCoordinator // 加载协调器 │ ├─ paginationCoordinator // 分页协调器 │ ├─ locationCoordinator // 位置协调器 │ ├─ searchCoordinator // 搜索协调器 │ ├─ chromeCoordinator // 工具栏协调器 │ ├─ annotationCoordinator // 标注协调器 │ └─ viewportMonitor // 视口变化监控 │ ├─ RDEPUBReaderPaginationCoordinator // 分页协调器 │ ├─ paginatePublication() // 入口 │ ├─ paginateMetadataOnly() // 后台元数据解析 │ └─ restoreBookPageMapIfPossible() // 缓存恢复 │ ├─ RDEPUBChapterLoader // 章节加载器 │ ├─ loadChapter() // 异步加载(Tier1→Tier2→全量构建) │ └─ loadChapterSynchronouslyForMigration() // 同步加载(快速打开用) │ └─ RDEPUBBookPageMap // 轻量页码映射 ├─ ~100KB/1000章,不持有 NSAttributedString └─ 支持增量刷新 (Builder pattern) ``` --- ## 6. WebView 渲染架构(Web 路径) ``` RDEPUBWebView (UIView) │ ├─ WKWebView (RDEPUBAnnotationWebView) │ ├─ 自定义选择菜单(拷贝/高亮/批注) │ └─ 禁用系统手势(滚动手势由原生控制) │ ├─ RDEPUBResourceURLSchemeHandler │ ├─ 协议: ss-reader://book/ │ ├─ 映射: URL → 本地 EPUB 解压文件 │ └─ 容错: 缺失资源返回空响应(不 404) │ ├─ JavaScript Bridge (6 种消息) │ ├─ ssReaderProgressionChanged // 阅读进度更新 │ ├─ ssReaderSelectionChanged // 文本选区变化 │ ├─ ssReaderInternalLink // EPUB 内部链接 │ ├─ ssReaderExternalLink // 外部链接 │ ├─ ssReaderJSError // JS 运行时错误 │ └─ ssReaderFixedLayoutReady // 固定布局加载完成 │ └─ 注入脚本 ├─ epub-bridge.js (502 行) // 核心桥接,分页/滚动/选区/高亮 ├─ cssInjector.js (45 行) // CSS 注入管理 ├─ WeReadApi.js (67 行) // 微信读书 API 兼容层 ├─ rangy-core.js / rangy-serializer.js // DOM Range 序列化 └─ CSS: default/replace/dark/latin // 4 套样式表 ``` --- ## 7. 翻页容器架构(RDReaderView) ``` RDReaderView (UIView) │ ├─ 三种翻页模式: │ ├─ .pageCurl → UIPageViewController (翻页动画) │ ├─ .horizontalScroll → UICollectionView (水平滑动) │ └─ .verticalScroll → UICollectionView (垂直滚动) │ ├─ 组合对象: │ ├─ RDReaderPagingController // 翻页状态管理、请求队列 │ ├─ RDReaderPreloadController // 页面预加载、缓存管理 │ ├─ RDReaderSpreadResolver // 双页展开计算 │ └─ RDReaderTapRegionHandler // 点击区域分类(左/中/右) │ ├─ 数据源协议: │ ├─ RDReaderPageProvider (新) // 格式无关,优先级高 │ └─ RDReaderDataSource (旧) // 遗留兼容,通过 Adapter 适配 │ └─ 手势流: 点击 → TapRegionHandler → 分类(左/中/右) ├─ 左 → goPreviousPage() ├─ 右 → goNextPage() └─ 中 → tapCenter() (切换工具栏) ``` --- ## 8. 配置与设置 ### RDEPUBReaderConfiguration ```swift struct RDEPUBReaderConfiguration { var fontSize: CGFloat // 默认 16 var fontChoice: FontChoice // 系统/宋体/楷体 var lineHeightMultiple: CGFloat // 默认 1.5 var numberOfColumns: Int // 1 或 2 var columnGap: CGFloat // 默认 20 var theme: ReaderTheme // 6 种主题 var displayType: RDReaderView.DisplayType // pageCurl/horizontal/vertical var onDemandChapterWindowSize: Int // 默认 3(奇数,最小 3,最大 15) var metadataParsingConcurrency: Int // 默认 CPU 核心数 var chapterWindowRadius: Int // 内存缓存窗口半径 } ``` ### 持久化 | 数据 | 存储方式 | Key 前缀 | |------|----------|----------| | 阅读位置 | UserDefaults | `ssreader.epub.location.{bookID}` | | 书签 | UserDefaults | `ssreader.epub.bookmarks.{bookID}` | | 高亮标注 | UserDefaults | `ssreader.epub.highlights.{bookID}` | | 全局设置 | UserDefaults | `ssreader.epub.settings` | | 章节摘要 | 磁盘文件 | `~/Caches/RDEPUBChapterSummaryCache/` | | 全书分页 | 磁盘文件 | `~/Caches/RDEPUBTextBookCache/` | --- ## 9. 目录结构 ``` Sources/RDReaderView/ ├── EPUBCore/ # EPUB 解析与 WebView 渲染 │ ├── Models/ # 数据模型 │ │ ├── RDEPUBAnnotationModels.swift │ │ ├── RDEPUBPaginationModels.swift │ │ └── RDEPUBReadingLocationModels.swift │ ├── Resources/ # JS/CSS/HTML 静态资源 │ │ ├── epub-bridge.js │ │ ├── cssInjector.js │ │ ├── WeReadApi.js │ │ ├── rangy-core.js / rangy-serializer.js │ │ ├── wxread-default.css / wxread-dark.css / wxread-replace-latin.css │ │ └── epub-fixed-layout.html │ ├── RDEPUBParser.swift # 核心解析器 │ ├── RDEPUBParser+Archive.swift # ZIP 解压 │ ├── RDEPUBParser+Package.swift # OPF 解析 │ ├── RDEPUBParser+TOC.swift # 目录解析 │ ├── RDEPUBParser+Resources.swift # 资源访问 │ ├── RDEPUBPublication.swift # 出版物门面 │ ├── RDEPUBResourceResolver.swift # URL 解析 │ ├── RDEPUBReadingSession.swift # 阅读会话状态机 │ ├── RDEPUBWebView.swift # WebView 主类 │ ├── RDEPUBWebView+*.swift # WebView 扩展(配置/重排/固定/搜索/JS桥接) │ ├── RDEPUBPaginator.swift # 离屏分页计算器 │ ├── RDEPUBSearchEngine.swift # 全文搜索引擎 │ └── ... │ ├── EPUBTextRendering/ # 文本渲染引擎 │ ├── Typesetter/ # 排版管线 │ │ ├── RDEPUBTypesettingPipeline.swift │ │ ├── RDEPUBHTMLNormalizer.swift │ │ ├── RDEPUBSemanticMarkerInjector.swift │ │ ├── RDEPUBFragmentMarkerInjector.swift │ │ ├── RDEPUBFontNormalizer.swift │ │ ├── RDEPUBAttachmentNormalizer.swift │ │ ├── RDEPUBStyleSheetComposer.swift │ │ ├── RDEPUBRenderDiagnosticsCollector.swift │ │ └── RDEPUBTextRendererSupport.swift │ ├── Pagination/ # 分页引擎 │ │ ├── RDEPUBChapterPageCounter.swift │ │ ├── RDEPUBCoreTextPageFrameFactory.swift │ │ ├── RDEPUBPageBreakPolicy.swift │ │ ├── RDEPUBTextLayoutFrame.swift │ │ └── ... │ ├── BuildPipeline/ # 全书构建 │ │ ├── RDEPUBTextBookBuilder.swift │ │ ├── RDEPUBTextBookModels.swift │ │ ├── RDEPUBTextBookCache.swift │ │ ├── RDEPUBChapterTailNormalizer.swift │ │ └── ... │ ├── RDEPUBTextRenderer.swift # 渲染器协议与类型定义 │ ├── RDEPUBDTCoreTextRenderer.swift # DTCoreText 渲染器实现 │ ├── RDEPUBChapterData.swift # 章节查询门面 │ ├── RDEPUBTextIndexTable.swift # 全书索引表 │ └── RDPlainTextBookBuilder.swift # 纯文本 (.txt) 构建器 │ ├── ReaderView/ # 通用翻页容器 │ ├── Paging/ # 翻页子系统 │ │ ├── RDReaderPagingController.swift │ │ ├── RDReaderPreloadController.swift │ │ ├── RDReaderSpreadResolver.swift │ │ └── RDReaderTapRegionHandler.swift │ ├── RDReaderView.swift # 主容器视图 │ ├── RDReaderView+*.swift # 扩展(CollectionView/PageCurl/ToolView/ContentAccess) │ ├── RDReaderFlowLayout.swift # 自定义 CollectionView 布局 │ ├── RDReaderContentCell.swift # 滚动模式 Cell │ └── RDReaderPageChildViewController.swift // 翻页模式子 VC │ └── EPUBUI/ # 阅读器 UI ├── ReaderController/ # 控制器与协调器 │ ├── ChapterRuntime/ # 章节运行时 │ │ ├── RDEPUBChapterLoader.swift │ │ ├── RDEPUBChapterRuntimeStore.swift │ │ ├── RDEPUBChapterSummaryDiskCache.swift │ │ ├── RDEPUBBookPageMap.swift │ │ ├── RDEPUBPageResolver.swift │ │ └── ... │ ├── RDEPUBReaderContext.swift // 共享状态中心 │ ├── RDEPUBReaderRuntime.swift // 运行时协调器(Facade) │ ├── RDEPUBReaderDependencies.swift // 依赖注入 │ ├── RDEPUBReaderLoadCoordinator.swift // 加载协调器 │ ├── RDEPUBReaderPaginationCoordinator.swift // 分页协调器 │ ├── RDEPUBReaderLocationCoordinator.swift // 位置协调器 │ ├── RDEPUBReaderSearchCoordinator.swift // 搜索协调器 │ ├── RDEPUBReaderChromeCoordinator.swift // 工具栏协调器 │ ├── RDEPUBReaderAnnotationCoordinator.swift // 标注协调器 │ ├── RDEPUBReaderAssemblyCoordinator.swift // 组装协调器 │ ├── RDEPUBReaderViewportMonitor.swift // 视口监控 │ └── ... ├── Settings/ # 设置面板 │ ├── RDEPUBReaderConfiguration.swift # 配置模型 │ ├── RDEPUBReaderSettings.swift # 持久化设置 │ ├── RDEPUBReaderSettingsViewController.swift # 设置面板 VC │ ├── RDEPUBReaderTheme.swift # 主题定义 │ └── ... ├── TextPage/ # 文本页面渲染 │ ├── RDEPUBTextContentView.swift # 文本内容视图(CoreText) │ ├── RDEPUBTextSelectionController.swift # 文本选择控制器 │ ├── RDEPUBPageInteractionController.swift # 点击交互控制器 │ ├── RDEPUBSelectionOverlayView.swift # 选区覆盖层 │ ├── RDEPUBTextAnnotationOverlay.swift # 标注覆盖层 │ ├── RDEPUBTextPageRenderView.swift # CoreText 绘制视图 │ └── ... ├── RDEPUBReaderController.swift # 主控制器(+ContentDelegates/DataSource/PublicAPI 等扩展) ├── RDEPUBReaderDelegate.swift # 公开委托协议 ├── RDEPUBReaderPersistence.swift # 持久化协议 └── ... ``` --- ## 10. 关键设计模式 | 模式 | 应用场景 | |------|----------| | **Coordinator** | 分页/标注/搜索/导航各一个协调器,解耦 Controller | | **Facade** | `RDEPUBPublication` 封装 `RDEPUBParser`,`RDEPUBChapterData` 封装章节查询 | | **Builder** | `RDEPUBBookPageMap.Builder` 增量构建页码映射 | | **Strategy** | `RDEPUBTextRenderer` 协议,可替换渲染器实现 | | **Pipeline** | `RDEPUBTextTypesetterPipeline` 排版管线(8 个逻辑阶段,封装为 5-6 个顶层调用) | | **State Machine** | `RDEPUBNavigatorState` 管理阅读器状态转换 | | **Adapter** | `RDReaderLegacyDataSourceAdapter` 适配旧数据源协议 | | **三级缓存** | 内存 → 磁盘摘要 → 全书分页,逐级降级 | | **Token 取消** | `paginationToken` 确保过期异步任务不干扰新任务 | | **Frozen Parameters** | 后台任务冻结 `renderSignature`,避免运行中参数漂移 |