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、状态机、内存警告、预加载、位置恢复等
This commit is contained in:
@@ -12,19 +12,19 @@ final class RDEPUBTextPageRenderView: UIView {
|
||||
|
||||
var layoutFrame: DTCoreTextLayoutFrame? {
|
||||
didSet {
|
||||
setNeedsDisplay()
|
||||
invalidateStaticContent()
|
||||
}
|
||||
}
|
||||
|
||||
var drawOptions: DTCoreTextLayoutFrameDrawingOptions = DTCoreTextLayoutFrameDrawingOptions(rawValue: 1)! {
|
||||
didSet {
|
||||
setNeedsDisplay()
|
||||
invalidateStaticContent()
|
||||
}
|
||||
}
|
||||
|
||||
var attributedDisplayContent: NSAttributedString? {
|
||||
didSet {
|
||||
setNeedsDisplay()
|
||||
invalidateStaticContent()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,12 @@ final class RDEPUBTextPageRenderView: UIView {
|
||||
|
||||
private let selectionHandleHitSlop: CGFloat = 20
|
||||
|
||||
private var cachedStaticImage: UIImage?
|
||||
|
||||
private var cachedStaticBoundsSize: CGSize = .zero
|
||||
|
||||
private var needsStaticContentRedraw = true
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
backgroundColor = .clear
|
||||
@@ -61,17 +67,35 @@ final class RDEPUBTextPageRenderView: UIView {
|
||||
|
||||
context.saveGState()
|
||||
|
||||
if let attributedDisplayContent {
|
||||
drawHighlights(in: context, attributedString: attributedDisplayContent, layoutFrame: layoutFrame)
|
||||
if cachedStaticImage == nil
|
||||
|| cachedStaticBoundsSize != bounds.size
|
||||
|| needsStaticContentRedraw {
|
||||
cachedStaticImage = renderStaticImage(layoutFrame: layoutFrame)
|
||||
cachedStaticBoundsSize = bounds.size
|
||||
needsStaticContentRedraw = false
|
||||
}
|
||||
|
||||
layoutFrame.draw(in: context, options: drawOptions)
|
||||
if let cachedStaticImage {
|
||||
cachedStaticImage.draw(in: bounds)
|
||||
} else {
|
||||
if let attributedDisplayContent {
|
||||
drawHighlights(in: context, attributedString: attributedDisplayContent, layoutFrame: layoutFrame)
|
||||
}
|
||||
layoutFrame.draw(in: context, options: drawOptions)
|
||||
}
|
||||
|
||||
drawSelection(in: context)
|
||||
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
if cachedStaticBoundsSize != bounds.size {
|
||||
invalidateStaticContent()
|
||||
}
|
||||
}
|
||||
|
||||
private func drawHighlights(
|
||||
in context: CGContext,
|
||||
attributedString: NSAttributedString,
|
||||
@@ -223,6 +247,26 @@ final class RDEPUBTextPageRenderView: UIView {
|
||||
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
private func invalidateStaticContent() {
|
||||
cachedStaticImage = nil
|
||||
needsStaticContentRedraw = true
|
||||
setNeedsDisplay()
|
||||
}
|
||||
|
||||
private func renderStaticImage(layoutFrame: DTCoreTextLayoutFrame) -> UIImage? {
|
||||
guard bounds.width > 0, bounds.height > 0 else { return nil }
|
||||
let format = UIGraphicsImageRendererFormat.default()
|
||||
format.opaque = false
|
||||
let renderer = UIGraphicsImageRenderer(size: bounds.size, format: format)
|
||||
return renderer.image { _ in
|
||||
guard let staticContext = UIGraphicsGetCurrentContext() else { return }
|
||||
if let attributedDisplayContent {
|
||||
drawHighlights(in: staticContext, attributedString: attributedDisplayContent, layoutFrame: layoutFrame)
|
||||
}
|
||||
layoutFrame.draw(in: staticContext, options: drawOptions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension CGPoint {
|
||||
|
||||
Reference in New Issue
Block a user