ReadViewSDK/Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBReaderAssemblyCoordinator.swift
shen 7de661eb54 feat: 架构整改 — Context拆分、Runtime拆分、异步章节加载、UI测试覆盖
Phase 1: Context 拆分
- 新增 RDEPUBReaderState/RDEPUBReaderEnvironment/RDEPUBReaderServices
- RDEPUBReaderContext 改为过渡门面,代理到 State/Environment/Services

Phase 2: Runtime 拆分
- 新增 RDEPUBPresentationRuntime 处理分页状态管理
- 新增 RDEPUBChapterWarmupOrchestrator 处理章节预热与加载编排
- RDEPUBReaderRuntime 从 1277 行收缩,公共 API 转发到新 facade

Phase 0.5: 性能优化
- prepareOnDemandChapter 支持异步模式(allowSynchronousLoad: false)
- extendPartialBookPageMapIfNeeded 改为 DispatchGroup 并发加载
- RDEPUBChapterOffsetMap.cfiMap 加 NSLock 保护数据竞争
- CFI Map 构建延迟到后台队列(scheduleDeferredCFIMapBuildIfNeeded)
- RDEPUBTextPageRenderView 引入静态位图缓存
- RDEPUBTextContentView 新增 loadingSpinner 占位页

Phase 3: 状态机
- 新增 RDEPUBNavigationStateMachine(含 DEBUG 合法转换校验)
- 新增 RDEPUBPaginationState 记录分页来源

Review 修复
- makeSummary 重复方法合并
- ensureNavigationTargetAvailable 同步路径加注释标记

UI 测试
- 新增 AsyncChapterLoadingTests(20 个测试,覆盖全部架构整改场景)
- 跨章节翻页、延迟 CFI、状态机、内存警告、预加载、位置恢复等
2026-06-23 08:17:08 +08:00

87 lines
4.1 KiB
Swift

import UIKit
final class RDEPUBReaderAssemblyCoordinator {
private unowned let context: RDEPUBReaderContext
init(context: RDEPUBReaderContext) {
self.context = context
}
func assembleInterface() {
guard let controller = context.controller,
let readerView = context.readerView else { return }
controller.view.backgroundColor = context.configuration.theme.contentBackgroundColor
setupReaderView(readerView, in: controller.view)
setupLoadingIndicator(controller.loadingIndicator, in: controller.view)
setupErrorLabel(controller.errorLabel, in: controller.view)
controller.delegate?.epubReader(controller, configureTopToolView: controller.topToolView)
#if DEBUG
print("[ReadViewDemo] assembleInterface: pageProvider=\(readerView.pageProvider != nil ? "set" : "nil"), numberOfPages=\(readerView.numberOfPages())")
#endif
}
func finishExternalTextBookLaunchIfNeeded() {
guard let runtime = context.runtime,
let controller = context.controller,
context.isExternalTextBook else {
return
}
let restoreLocation = context.currentBookIdentifier.flatMap { context.persistence?.loadLocation(for: $0) }
if let id = context.currentBookIdentifier {
context.activeBookmarks = context.persistence?.loadBookmarks(for: id) ?? []
context.activeHighlights = context.persistence?.loadHighlights(for: id) ?? []
}
if let textBook = controller.textBook {
#if DEBUG
print("[ReadViewDemo] finishExternalTextBook: applying textBook with \(textBook.pages.count) pages")
#endif
runtime.applyTextBook(textBook, restoreLocation: restoreLocation)
#if DEBUG
print("[ReadViewDemo] finishExternalTextBook: after applyTextBook, numberOfPages=\(context.readerView?.numberOfPages() ?? -1)")
#endif
} else {
runtime.finishPagination(restoreLocation: restoreLocation)
}
}
private func setupReaderView(_ readerView: RDReaderView, in containerView: UIView) {
readerView.pageProvider = context.controller
readerView.delegate = context.controller
readerView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(readerView)
NSLayoutConstraint.activate([
readerView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
readerView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
readerView.topAnchor.constraint(equalTo: containerView.topAnchor),
readerView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
])
readerView.register(contentView: RDEPUBTextContentView.self, contentViewWithReuseIdentifier: NSStringFromClass(RDEPUBTextContentView.self))
readerView.register(contentView: RDEPUBWebContentView.self, contentViewWithReuseIdentifier: NSStringFromClass(RDEPUBWebContentView.self))
context.controller?.applyReaderViewConfiguration()
}
private func setupLoadingIndicator(_ loadingIndicator: UIActivityIndicatorView, in containerView: UIView) {
loadingIndicator.hidesWhenStopped = true
loadingIndicator.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(loadingIndicator)
NSLayoutConstraint.activate([
loadingIndicator.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
loadingIndicator.centerYAnchor.constraint(equalTo: containerView.centerYAnchor)
])
}
private func setupErrorLabel(_ errorLabel: UILabel, in containerView: UIView) {
errorLabel.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(errorLabel)
NSLayoutConstraint.activate([
errorLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 24),
errorLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -24),
errorLabel.centerYAnchor.constraint(equalTo: containerView.centerYAnchor)
])
}
}