# ReadViewSDK UML 类图 > 最后更新:2026-06-09 > 图表格式:Mermaid(可在 GitHub / VS Code / Mermaid Live Editor 中渲染) --- ## 1. 整体模块关系 ```mermaid graph TB subgraph EPUBCore Parser[RDEPUBParser] Pub[RDEPUBPublication] Resolver[RDEPUBResourceResolver] Session[RDEPUBReadingSession] WebView[RDEPUBWebView] Paginator[RDEPUBPaginator] SearchEngine[RDEPUBHTMLSearchEngine] end subgraph EPUBTextRendering Builder[RDEPUBTextBookBuilder] Renderer[RDEPUBDTCoreTextRenderer] Pipeline[RDEPUBTextTypesetterPipeline] Counter[RDEPUBChapterPageCounter] FrameFactory[RDEPUBCoreTextPageFrameFactory] BreakPolicy[RDEPUBPageBreakPolicy] BookCache[RDEPUBTextBookCache] ChapterData[RDEPUBChapterData] IndexTable[RDEPUBTextIndexTable] end subgraph RDEpubReaderView ReaderView[RDEpubReaderView] FlowLayout[RDEpubReaderFlowLayout] Preload[RDEpubReaderPreloadController] Spread[RDEpubReaderSpreadResolver] TapRegion[RDEpubReaderTapRegionHandler] end subgraph EPUBUI Controller[RDEPUBReaderController] Context[RDEPUBReaderContext] Runtime[RDEPUBReaderRuntime] PaginationCoord[RDEPUBReaderPaginationCoordinator] Loader[RDEPUBChapterLoader] RuntimeStore[RDEPUBChapterRuntimeStore] DiskCache[RDEPUBChapterSummaryDiskCache] PageMap[RDEPUBBookPageMap] Config[RDEPUBReaderConfiguration] end Parser --> Pub Pub --> Resolver Session --> Pub WebView --> Pub WebView --> Resolver Paginator --> Parser Builder --> Renderer Builder --> Pipeline Builder --> Counter Counter --> FrameFactory FrameFactory --> BreakPolicy Builder --> BookCache ChapterData --> IndexTable Controller --> Context Context --> Runtime Runtime --> Loader Runtime --> RuntimeStore Runtime --> DiskCache PaginationCoord --> Context PaginationCoord --> PageMap Loader --> RuntimeStore Loader --> DiskCache Controller --> ReaderView Controller --> Session Controller --> Builder ReaderView --> FlowLayout ReaderView --> Preload ReaderView --> Spread ReaderView --> TapRegion ``` --- ## 2. EPUBCore 核心类图 ```mermaid classDiagram class RDEPUBParser { +metadata: RDEPUBMetadata +manifest: [String: RDEPUBManifestItem] +spine: [RDEPUBSpineItem] +tableOfContents: [EPUBTableOfContentsItem] +extractionRootURL: URL +opfURL: URL +opfDirectoryURL: URL +parse(epubURL: URL) throws +makePublication() RDEPUBPublication +htmlString(forRelativePath:) String? +htmlString(forSpineIndex:) String? +coverImage() UIImage? +resourceURL(forRelativePath:) URL +fileURL(forRelativePath:) URL? } class RDEPUBPublication { +parser: RDEPUBParser +resourceResolver: RDEPUBResourceResolver +metadata: RDEPUBMetadata +manifest: [RDEPUBManifestItem] +spine: [RDEPUBSpineItem] +tableOfContents: [EPUBTableOfContentsItem] +layout: RDEPUBLayout +readingProfile: RDEPUBReadingProfile +readingProgression: RDEPUBReadingProgression +bookIdentifier: String? +fixedLayoutSpreadEnabled(preferences:viewportSize:) Bool +makeFixedSpreads(preferences:viewportSize:) [EPUBFixedSpread] } class RDEPUBResourceResolver { +parser: RDEPUBParser +fileURL(forRelativePath:) URL? +resourceURL(forRelativePath:) URL +normalizedHref(_:relativeToSpineIndex:) String? +normalizedLocation(_:relativeToSpineIndex:bookIdentifier:) RDEPUBLocation? +spineIndex(forNormalizedHref:) Int? +spineIndex(for:) Int? } class RDEPUBReadingSession { +publication: RDEPUBPublication +navigatorState: RDEPUBNavigatorState +activePages: [EPUBPage] +activeChapters: [EPUBChapterInfo] +currentViewport: RDEPUBViewport? +currentReadingContext: RDEPUBReadingContext? +transition(to: RDEPUBNavigatorState) +setActiveSnapshot(_:) +queueNavigation(to:relativeToSpineIndex:bookIdentifier:) Int +updateReadingContext(...) +makePaginationSnapshot(pageCounts:preferences:layoutContext:) } class RDEPUBWebView { +publication: RDEPUBPublication? +currentRenderRequest: RDEPUBRenderRequest? +webView: WKWebView +delegate: RDEPUBWebViewDelegate? +load(publication:request:) +reset() } class RDEPUBNavigatorState { <> initializing loading idle jumping moving repaginating +isStableForSnapshotApplication: Bool } RDEPUBParser --> RDEPUBPublication : creates RDEPUBPublication --> RDEPUBResourceResolver : owns RDEPUBReadingSession --> RDEPUBPublication : wraps RDEPUBReadingSession --> RDEPUBNavigatorState : manages RDEPUBWebView --> RDEPUBPublication : uses RDEPUBWebView --> RDEPUBResourceResolver : uses ``` --- ## 3. EPUBTextRendering 类图 ```mermaid classDiagram class RDEPUBTextBookBuilder { +renderer: RDEPUBTextRenderer +cache: RDEPUBTextBookCache? +layoutConfig: RDEPUBTextLayoutConfig +renderPipeline: RDEPUBChapterRenderPipeline +paginationPipeline: RDEPUBChapterPaginationPipeline +tailNormalizer: RDEPUBChapterTailNormalizer +build(parser:publication:pageSize:style:) throws RDEPUBTextBook +buildChapter(parser:publication:spineIndex:pageSize:style:) throws RDEPUBTextChapterBuildResult? } class RDEPUBTextRenderer { <> +renderChapter(request:) throws RDEPUBRenderedChapterContent } class RDEPUBDTCoreTextRenderer { +renderChapter(request:) throws RDEPUBRenderedChapterContent +renderChapter(html:baseURL:style:) throws RDEPUBRenderedChapterContent } class RDEPUBTextTypesetterPipeline { +makeRequest(from: RDEPUBTypesettingInput) RDEPUBTypesettingOutput } class RDEPUBChapterPageCounter { +factory: RDEPUBCoreTextPageFrameFactory +layoutFrames(fragmentOffsets:) [RDEPUBTextLayoutFrame] } class RDEPUBCoreTextPageFrameFactory { +attributedString: NSAttributedString +pageSize: CGSize +config: RDEPUBTextLayoutConfig +makeFrames(...) [RDEPUBTextLayoutFrame] } class RDEPUBPageBreakPolicy { +lineIsInAvoidPageBreakInsideBlock(_:) Bool +lineIsInKeepWithNextBlock(_:) Bool +adjustedRange(from:totalLength:lineRanges:factory:) RDEPUBPageBreakDecision } class RDEPUBTextBook { +chapters: [RDEPUBTextChapter] +pages: [RDEPUBTextPage] +indexTable: RDEPUBTextIndexTable +positionConverter: RDEPUBTextPositionConverter +chapterData(for href:) RDEPUBChapterData? +chapterData(forPageNumber:) RDEPUBChapterData? } class RDEPUBTextChapter { +chapterIndex: Int +spineIndex: Int +href: String +title: String +attributedContent: NSAttributedString +fragmentOffsets: [String: Int] +pages: [RDEPUBTextPage] } class RDEPUBTextPage { +absolutePageIndex: Int +chapterIndex: Int +spineIndex: Int +contentRange: NSRange +metadata: RDEPUBTextPageMetadata } class RDEPUBTextBookCache { +schemaVersion: Int +load(key:) [String: RDEPUBTextChapterPaginationCache]? +save(_:key:) +invalidateAll() } class RDEPUBChapterData { +chapterIndex: Int +spineIndex: Int +href: String +title: String +pageCount: Int +page(containing:) RDEPUBTextPage? +pageNumber(containing:) Int? +absoluteRange(for location:) ClosedRange? } class RDEPUBTextIndexTable { +chapterStartOffsets: [Int] +totalCharacterCount: Int +anchor(forAbsoluteIndex:in:) RDEPUBTextAnchor +pageNumber(for:in:) Int +globalIndex(for:) Int } RDEPUBTextBookBuilder --> RDEPUBTextRenderer : uses RDEPUBDTCoreTextRenderer ..|> RDEPUBTextRenderer RDEPUBTextBookBuilder --> RDEPUBTextTypesetterPipeline : uses RDEPUBTextBookBuilder --> RDEPUBChapterPageCounter : uses RDEPUBChapterPageCounter --> RDEPUBCoreTextPageFrameFactory : uses RDEPUBCoreTextPageFrameFactory --> RDEPUBPageBreakPolicy : uses RDEPUBTextBookBuilder --> RDEPUBTextBookCache : uses RDEPUBTextBook --> RDEPUBTextChapter : contains RDEPUBTextChapter --> RDEPUBTextPage : contains RDEPUBTextBook --> RDEPUBTextIndexTable : computes RDEPUBChapterData --> RDEPUBTextChapter : wraps RDEPUBChapterData --> RDEPUBTextIndexTable : uses ``` --- ## 4. ReaderView 类图 ```mermaid classDiagram class RDEpubReaderView { +currentPage: Int +currentDisplayType: DisplayType +pageProvider: RDEpubReaderPageProvider? +delegate: RDEpubReaderDelegate? +landscapeDualPageEnabled: Bool +pageDirection: PageDirection +coverPageIndex: Int? +pagesPerScreen: Int +transitionToPage(pageNum:animated:) +reloadData() +switchReaderDisplayType(_:) } class DisplayType { <> pageCurl horizontalScroll verticalScroll } class RDEpubReaderPageProvider { <> +numberOfPages(in:) Int +readerView(_:viewForPageAt:reusableView:) UIView +pageIdentifier(in:index:) String? } class RDEpubReaderFlowLayout { +displayType: DisplayType +isLandscapeDualPage: Bool +coverPageIndex: Int? +pagesPerScreen: Int +currentPage: Int } class RDEpubReaderPreloadController { +radius: Int +pageViewForDisplay(pageNum:environment:contentViewProvider:) UIView? +takePreloadedView(for:) UIView? +prime(around:preferredForward:parentView:environment:contentViewProvider:) +invalidate(environment:) } class RDEpubReaderSpreadResolver { +isFullScreenPage(...) Bool +dualPagePair(for:totalPages:coverPageIndex:) (Int, Int?) +nextPage(from:totalPages:pagesPerScreen:coverPageIndex:forward:) Int? } class RDEpubReaderTapRegionHandler { +resolveTapEvent(point:viewFrame:isToolViewVisible:) TapEvent } class TapEvent { <> none left center right } class RDEpubReaderPagingController { +isTransitioning: Bool +didBuildUI: Bool +pendingTransitionRequest: PageTransitionRequest? +shouldQueuePageTransition(_:currentDisplayType:) Bool +finishPageCurlTransition() PageTransitionRequest? } class RDEpubReaderContentCell { +containerView: UIView? } class RDEpubReaderPageChildViewController { +contentView: UIView? +pageNum: Int } RDEpubReaderView --> DisplayType : uses RDEpubReaderView --> RDEpubReaderPageProvider : delegates RDEpubReaderView --> RDEpubReaderFlowLayout : owns RDEpubReaderView --> RDEpubReaderPreloadController : owns RDEpubReaderView --> RDEpubReaderSpreadResolver : owns RDEpubReaderView --> RDEpubReaderTapRegionHandler : owns RDEpubReaderView --> RDEpubReaderPagingController : owns RDEpubReaderView --> RDEpubReaderContentCell : creates RDEpubReaderView --> RDEpubReaderPageChildViewController : creates RDEpubReaderTapRegionHandler --> TapEvent : produces ``` --- ## 5. EPUBUI 控制器与运行时类图 ```mermaid classDiagram class RDEPUBReaderController { +delegate: RDEPUBReaderDelegate? +configuration: RDEPUBReaderConfiguration +currentLocation: RDEPUBLocation? +currentPageNumber: Int? +currentSelection: RDEPUBSelection? +highlights: [RDEPUBHighlight] +bookmarks: [RDEPUBBookmark] +tableOfContents: [EPUBTableOfContentsItem] +readerView: RDEpubReaderView +readerContext: RDEPUBReaderContext +runtime: RDEPUBReaderRuntime? } class RDEPUBReaderContext { +parser: RDEPUBParser? +publication: RDEPUBPublication? +readingSession: RDEPUBReadingSession? +textBook: RDEPUBTextBook? +bookPageMap: RDEPUBBookPageMap? +configuration: RDEPUBReaderConfiguration +persistence: RDEPUBReaderPersistence? +currentRenderSignature() String +chapterCacheKey(forSpineIndex:) RDEPUBChapterCacheKey +chapterCacheKey(forSpineIndex:precomputedContentHash:renderSignature:) RDEPUBChapterCacheKey +makeTextBookBuilder(layoutConfig:) RDEPUBTextBookBuilder } class RDEPUBReaderRuntime { +chapterLoader: RDEPUBChapterLoader +chapterRuntimeStore: RDEPUBChapterRuntimeStore +summaryDiskCache: RDEPUBChapterSummaryDiskCache +pageResolver: RDEPUBPageResolver +loadCoordinator: RDEPUBReaderLoadCoordinator +paginationCoordinator: RDEPUBReaderPaginationCoordinator +locationCoordinator: RDEPUBReaderLocationCoordinator +searchCoordinator: RDEPUBReaderSearchCoordinator +chromeCoordinator: RDEPUBReaderChromeCoordinator +annotationCoordinator: RDEPUBReaderAnnotationCoordinator +viewportMonitor: RDEPUBReaderViewportMonitor +applyBookPageMap(_:restoreLocation:) +refreshBookPageMapInPlace(_:) } class RDEPUBReaderPaginationCoordinator { -context: RDEPUBReaderContext +paginatePublication(restoreLocation:) +repaginatePreservingCurrentLocation() } class RDEPUBMetadataParseWorker { -context: RDEPUBReaderContext -cancellationController: RDEPUBMetadataParseCancellationController +start(token:restoreLocation:) } class RDEPUBMetadataParseCancellationController { +token: UUID +attach(queue:) +cancel() +isCancelled: Bool } class RDEPUBReaderLoadCoordinator { -context: RDEPUBReaderContext +startInitialLoadIfNeeded() -loadPublication() -applyParsedPublication() } class RDEPUBReaderSearchCoordinator { -context: RDEPUBReaderContext +search(keyword:) +goToNextMatch() +goToPreviousMatch() +clearSearch() } class RDEPUBReaderAnnotationCoordinator { -context: RDEPUBReaderContext +addHighlight(from:) +removeHighlight(id:) +toggleBookmark(at:) +handleSelectionMenuAction(_:) } class RDEPUBChapterLoader { -context: RDEPUBReaderContext +loadChapter(spineIndex:store:) async throws RDEPUBRuntimeChapter +loadChapterSynchronouslyForMigration(spineIndex:store:) throws RDEPUBRuntimeChapter } class RDEPUBChapterRuntimeStore { +chapterDataCache: [Int: RDEPUBRuntimeChapter] +pageCountCache: RDEPUBPageCountCache +imageCache: NSCache +setCurrentChapter(spineIndex:totalSpineCount:windowRadius:) +chapterData(for:) RDEPUBRuntimeChapter? +pageCount(for:) RDEPUBRuntimePageCount? } class RDEPUBChapterSummaryDiskCache { +cacheDirectory: URL +read(for:) RDEPUBChapterSummary? +readAll(keys:) ([Int: RDEPUBChapterSummary], RDEPUBBookPageMap.Builder) +write(summary:for:) +flushPendingWrites() +isCacheComplete(keys:) Bool } class RDEPUBBookPageMap { +totalChapters: Int +totalPages: Int +chapters: [EPUBChapterInfo] +pages: [EPUBPage] } class RDEPUBChapterCacheKey { +bookID: String +spineIndex: Int +renderSignature: String +chapterContentHash: String } class RDEPUBReaderConfiguration { +fontSize: CGFloat +fontChoice: FontChoice +lineHeightMultiple: CGFloat +numberOfColumns: Int +theme: ReaderTheme +displayType: DisplayType +onDemandChapterWindowSize: Int +metadataParsingConcurrency: Int } RDEPUBReaderController --> RDEPUBReaderContext : owns RDEPUBReaderController --> RDEPUBReaderRuntime : owns RDEPUBReaderController --> RDEPUBReaderPaginationCoordinator : owns RDEPUBReaderContext --> RDEPUBReaderConfiguration : holds RDEPUBReaderRuntime --> RDEPUBChapterLoader : owns RDEPUBReaderRuntime --> RDEPUBChapterRuntimeStore : owns RDEPUBReaderRuntime --> RDEPUBChapterSummaryDiskCache : owns RDEPUBReaderRuntime --> RDEPUBReaderLoadCoordinator : owns RDEPUBReaderRuntime --> RDEPUBReaderSearchCoordinator : owns RDEPUBReaderRuntime --> RDEPUBReaderAnnotationCoordinator : owns RDEPUBReaderPaginationCoordinator --> RDEPUBReaderContext : uses RDEPUBChapterLoader --> RDEPUBReaderContext : uses RDEPUBChapterLoader --> RDEPUBChapterRuntimeStore : uses RDEPUBChapterLoader --> RDEPUBChapterSummaryDiskCache : uses RDEPUBChapterSummaryDiskCache --> RDEPUBChapterCacheKey : uses RDEPUBBookPageMap --> EPUBChapterInfo : contains RDEPUBBookPageMap --> EPUBPage : contains ``` --- ## 6. 数据模型关系图 ```mermaid classDiagram class RDEPUBLocation { +bookIdentifier: String? +href: String +progression: Double +lastProgression: Double? +fragment: String? +rangeAnchor: RDEPUBTextRangeAnchor? } class RDEPUBTextAnchor { +fileIndex: Int +row: Int +column: Int +chapterOffset: Int +fragmentID: String? +spineIndex: Int } class RDEPUBTextRangeAnchor { +start: RDEPUBTextAnchor +end: RDEPUBTextAnchor +nsRange: NSRange } class RDEPUBSelection { +bookIdentifier: String? +location: RDEPUBLocation +text: String +rangeInfo: String? +createdAt: Date } class RDEPUBHighlight { +id: String +bookIdentifier: String? +location: RDEPUBLocation +text: String +style: RDEPUBHighlightStyle +color: String +note: String? +createdAt: Date +uiColor: UIColor } class RDEPUBBookmark { +id: String +bookIdentifier: String? +location: RDEPUBLocation +chapterTitle: String? +note: String? +createdAt: Date } class RDEPUBAnnotation { +id: String +bookIdentifier: String? +kind: RDEPUBAnnotationKind +location: RDEPUBLocation +text: String? +rangeInfo: String? +chapterTitle: String? +createdAt: Date +bookmark: RDEPUBBookmark? +highlight: RDEPUBHighlight? } class EPUBPage { +spineIndex: Int +chapterIndex: Int +pageIndexInChapter: Int +totalPagesInChapter: Int +chapterTitle: String +fixedSpread: EPUBFixedSpread? } class EPUBChapterInfo { +spineIndex: Int +title: String +pageCount: Int } class RDEPUBViewport { +resources: [RDEPUBViewportResource] +visiblePageNumber: Int +chapterIndex: Int? +isFixedLayout: Bool } class RDEPUBReadingContext { +location: RDEPUBLocation +viewport: RDEPUBViewport +pageNumber: Int +chapterIndex: Int? } RDEPUBLocation --> RDEPUBTextRangeAnchor : optional RDEPUBTextRangeAnchor --> RDEPUBTextAnchor : start/end RDEPUBSelection --> RDEPUBLocation : references RDEPUBHighlight --> RDEPUBLocation : references RDEPUBBookmark --> RDEPUBLocation : references RDEPUBAnnotation --> RDEPUBLocation : references RDEPUBReadingContext --> RDEPUBLocation : holds RDEPUBReadingContext --> RDEPUBViewport : holds ``` --- ## 7. 后台解析并发模型 ```mermaid sequenceDiagram participant Main as Main Thread participant BG as Background Queue participant OQ as OperationQueue (N workers) participant Disk as Disk Cache Main->>BG: RDEPUBMetadataParseWorker.start(token) BG->>BG: 预计算 contentHash (串行) BG->>Disk: readAll(catalog) 批量读取缓存 BG->>Main: refreshBookPageMapInPlace (缓存部分) BG->>BG: waitForReadingInteractionToSettle (0.8s) loop 每个未缓存章节 BG->>OQ: addOperation(spineIndex) OQ->>OQ: buildChapter (渲染+分页) OQ->>OQ: chapterCacheKey (复用预计算hash) OQ->>Disk: write(summary) 异步写盘 OQ->>OQ: resultLock: 累加结果 alt 每32章或最后一章 OQ->>Main: refreshBookPageMapInPlace end end OQ->>BG: waitUntilAllOperationsAreFinished BG->>Disk: flushPendingWrites BG->>Main: refreshBookPageMapInPlace (最终) ``` --- ## 8. 缓存键与失效策略 ```mermaid graph LR subgraph CacheKey["RDEPUBChapterCacheKey"] A[bookID] B[spineIndex] C[renderSignature] D[chapterContentHash] end subgraph RenderSignature["renderSignature 组成"] E[fontName] F[fontSize] G[lineHeightMultiple] H[lineSpacing] I[layoutConfig.cacheSignature] J[schemaVersion] end subgraph CacheSignature["layoutConfig.cacheSignature"] K[frameWidth] L[frameHeight] M[edgeInsets] N[numberOfColumns] O[columnGap] P[avoidOrphans/Widows] Q[hyphenation] R[imageMaxHeightRatio] end C --> E & F & G & H & I & J I --> K & L & M & N & O & P & Q & R style CacheKey fill:#e1f5fe style RenderSignature fill:#fff3e0 style CacheSignature fill:#e8f5e9 ```