# 微信读书 EPUB 阅读器 — 核心架构与数据流图 > 逆向工程目标: WeRead v10.0.3 (Build 79), arm64, 63MB 主二进制 --- ## 一、模块拓扑与依赖关系图 ### 1.1 App 整体模块拓扑 ``` ┌─────────────────────────────────────────────────────────────────────┐ │ WeRead.app (iOS/macOS Catalyst) │ │ 主二进制: 63MB, Mach-O arm64 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ 主二进制 (WeRead) │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │ │ │ │ │ 阅读器模块 │ │ 书架模块 │ │ 其他业务模块 │ │ │ │ │ │ WRReader* │ │ WRBookshelf* │ │ WRDiscover/WRMarket │ │ │ │ │ │ WREpub* │ │ WRBook* │ │ WRAccount/WRSetting │ │ │ │ │ │ DTCoreText* │ │ │ │ WRCommunity/WRChat │ │ │ │ │ │ WRPage* │ │ │ │ WRActivity/WRAI │ │ │ │ │ │ WRChapter* │ │ │ │ ... │ │ │ │ │ └──────────────┘ └──────────────┘ └──────────────────────┘ │ │ │ │ ┌──────────────────────────────────────────────────────────┐│ │ │ │ │ ObjC 元数据 (未剥离): ││ │ │ │ │ __objc_classlist: 0xb230 (类列表) ││ │ │ │ │ __objc_classname: 0x25a35 (~154KB, 17584 个类名) ││ │ │ │ │ __objc_methname: 0x24ba5a (~2.4MB 方法名) ││ │ │ │ │ __objc_methtype: 0x382bc (~226KB 类型编码) ││ │ │ │ └──────────────────────────────────────────────────────────┘│ │ │ └──────────────────────────────────────────────────────────────┘ │ │ │ │ ┌──────────────────── Frameworks ────────────────────────────┐ │ │ │ │ │ │ │ ┌─────────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ │ │ hermes.framework│ │QMUIKit.fwk │ │WRSharedMod* │ │ │ │ │ │ (RN JS 引擎) │ │(腾讯UI组件库) │ │(共享业务模块) │ │ │ │ │ │ v0.12.0 │ │ │ │ │ │ │ │ │ │ Facebook Hermes │ │ QMUI 配色/ │ │ 书架/发现/ │ │ │ │ │ │ 替代 JSC │ │ 主题/组件 │ │ 社区/活动 │ │ │ │ │ └─────────────────┘ └──────────────┘ └──────────────┘ │ │ │ │ │ │ │ │ ┌─────────────────┐ ┌──────────────┐ │ │ │ │ │ WRUICore.fwk │ │CocoaMarkdown │ │ │ │ │ │ (UI核心+字体) │ │(Markdown渲染)│ │ │ │ │ │ 含 WeReadLS 系列│ │ │ │ │ │ │ │ 含 SharpGrotesk │ │ │ │ │ │ │ └─────────────────┘ └──────────────┘ │ │ │ └────────────────────────────────────────────────────────────┘ │ │ │ │ ┌──────────────────── RNBundles (39个 .hbc) ─────────────────┐ │ │ │ aiSearch bookDetail browse community discoverV2 market │ │ │ │ account medals memberDetail gameCenterHome freeBooks ... │ │ │ │ (Hermes 编译的 HBC 字节码, 业务动态化) │ │ │ └────────────────────────────────────────────────────────────┘ │ │ │ │ ┌──────────────────── Bundles ────────────────────────────────┐ │ │ │ OnePatch.bundle (ObjC热修复, JS驱动) │ │ │ │ MidasIAPSDK.bundle (腾讯米大师支付) │ │ │ │ Sentry.bundle (崩溃监控) │ │ │ │ QCloudCOSXML.bundle (腾讯云COS存储) │ │ │ │ MJRefresh.bundle (下拉刷新) │ │ │ │ mathFonts.bundle (数学字体: Latin Modern/XITS/TeX Gyre) │ │ │ └────────────────────────────────────────────────────────────┘ │ │ │ │ ┌──────────────────── App Extensions ─────────────────────────┐ │ │ │ ActionExtension.appex (操作扩展) │ │ │ │ ModernWidgetExtension.appex (新版小组件 WidgetKit) │ │ │ │ NotificationService.appex (通知服务) │ │ │ │ ShareExtension.appex (分享扩展) │ │ │ │ Widget.appex (旧版小组件) │ │ │ └────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────┘ 系统框架依赖: ┌────────────────────────────────────────────────────────────┐ │ CoreText CTTypesetter / CTFramesetter / CTFrame │ │ CTLine / CTRun / CTFontCreatePathForGlyph │ │ CoreGraphics CGContext / CGPath / CGAffineTransform │ │ UIKit UIView / UIPageViewController / Gesture │ │ WebKit WKWebView (仅公众号/文集路径) │ │ Foundation NSAttributedString / NSXMLParser │ │ Security Keychain (加密密钥存储) │ │ AVFoundation TTS 朗读音频播放 │ │ PencilKit Apple Pencil 手写笔记 │ │ ImageIO 图片解码与缓存 │ └────────────────────────────────────────────────────────────┘ ``` ### 1.2 EPUB 渲染模块内部依赖图 ``` WRReaderViewController (431 methods, 阅读器主控制器) │ ├─→ WRPageViewController (80 methods, 翻页控制器) │ │ 基于 UIPageViewController 封装 │ │ 支持 UIPageCurl (仿真翻页) / Scroll (左右滑动) │ │ 包含 4 个故障修复补丁 │ │ │ └─→ WRPageView (105 methods, 页面渲染视图) │ │ 继承 UIView, drawRect: 中直接 CGContext 绘制 │ │ 不使用 UILabel / UITextView │ │ │ ├─→ WRCoreTextLayoutFrame (4 methods, 单页排版帧) │ │ │ drawInContext:image:size:inRect:position: │ │ │ avoidPageBreakInsideByRemovingLastLinesIfNeeded │ │ │ getRenderHeight / lines │ │ │ │ │ └─→ DTCoreTextLayoutFrame (56 methods, DTCoreText 排版帧) │ │ │ 封装 CTFrame, 提取 CTLine 列表 │ │ │ hitTestWithPoint: (文本选择) │ │ │ │ │ └─→ DTCoreTextLayoutLine (61 methods, 排版行) │ │ │ 封装 CTLine, 提取 CTRun 列表 │ │ │ typographicBounds / trailingWhitespaceWidth │ │ │ │ │ └─→ DTCoreTextGlyphRun (23 methods, 字形渲染) │ │ │ 封装 CTRun │ │ └─→ newPathWithGlyphs (CTFontCreatePathForGlyph) │ │ │ └─→ 文本选择 (CoreText hit test) │ CTLineGetStringIndexForPosition (坐标→字符索引) │ CTLineGetOffsetForStringIndex (字符索引→坐标) │ ├─→ WRChapterData (125 methods, 章节数据模型) │ │ 存储排版后的 NSAttributedString │ │ 管理高亮/下划线/书评标注 │ │ │ ├─→ WRCoreTextLayouter (20 methods, WeRead CoreText 封装) │ │ │ 管理 CTTypesetter / CTFramesetter │ │ │ pageBackgroundImageAtRange:themeBgColor: │ │ │ resizedImageForImagePath:rect:position:sizePattern:darkMode:themeBgColor: │ │ │ │ │ └─→ DTCoreTextLayouter (12 methods, DTCoreText 排版器) │ │ │ 封装 CTTypesetter, 缓存 DTCoreTextLayoutFrame │ │ │ │ │ └─→ CTTypesetter / CTFramesetter (系统 CoreText) │ │ │ └─→ WRChapterPageCount (40 methods, 分页计算) │ rangeValueWithPageInfo: (计算每页 NSRange) │ currentCacheKeyWithBookId: (缓存键) │ ├─→ WREpubTypesetter (9 methods, EPUB 排版器入口) │ │ attributeStringWithFilePath:... (核心类方法) │ │ │ └─→ DTHTMLAttributedStringBuilder (25 methods, HTML→NSAttributedString) │ │ _buildString (核心构建) │ │ parser:didStartElement:attributes:position: (SAX 回调) │ │ parser:foundCharacters:position: │ │ parserDidEndDocument: │ │ │ ├─→ DTHTMLParser (19 methods, HTML SAX 解析器) │ │ 基于 libxml2 封装 │ │ │ ├─→ DTHTMLElement (168 methods, HTML 元素模型) │ │ │ applyStyleDictionary:isLatinLanguageBook: (CSS 应用) │ │ │ attributedString (生成富文本) │ │ │ interpretAttributes (属性解析) │ │ │ │ │ ├─→ DTCoreTextFontDescriptor (字体描述) │ │ ├─→ DTCoreTextParagraphStyle (段落样式) │ │ ├─→ DTCSSStylesheet (CSS 样式表) │ │ └─→ DTTextAttachment (附件: 图片/视频/iframe) │ │ │ └─→ CSS 级联: default.css → replace.css → dark.css → epub内嵌 → 用户设置 │ ├─→ WREpubParser (12 methods, EPUB 文件解析) │ │ 解析 container.xml / content.opf / toc.ncx │ │ 输出: 章节列表 + 资源映射 │ └─→ WREpubPositionConverter (22 methods, 位置转换) │ 文件位置 ↔ 字符位置 映射 │ ├─→ WRChapterDownloadManger (47 methods, 章节下载) │ │ RAC 任务队列 + 并发控制 │ │ │ ├─→ WRBookNetwork (155 methods, 网络层) │ │ │ loadTarForEpubBookId:chapter:isPreload: (下载加密 ZIP) │ │ │ handleUnzipWithBookId:zipPath:encryptKey:plainBookDirectory: (解密解压) │ │ │ processEncryptedBookFileAtPath:encryptKey:book:chapterUid: (文件处理) │ │ │ fileContentWithChapter:book:shouldRemoveHtmlTags: (读取内容) │ │ │ │ │ └─→ WREncryptedFileManager (7 methods, DRM 管理) │ │ decryptContentsOfFile:forBookId:isFileLost: (AES 解密) │ │ encryptFileForBookId:originalEncryptKey:atPath:toPath: (本地加密) │ │ keyForBookId: (密钥管理) │ │ │ └─→ WRPreloadBookManager (76 methods, 预加载管理) │ saveEncryptKey:forPath:bookId: (密钥缓存) │ _preloadWholeBook:scene: (整书预加载) │ ├─→ 标注系统 │ ├─→ WRPageHighlight (3 methods, 高亮) │ ├─→ WRPageUnderline (10 methods, 下划线: solid/dashed/wavy/dotted) │ ├─→ WRPageMark (25 methods, 书签标注) │ ├─→ WRBookmark (58 methods, 书签模型) │ └─→ WRReaderPencilNoteManager (11 class methods, Pencil 笔记) │ └─→ 附件系统 (WRPageAttachments) ├─→ WRPageImageAttachment (27 methods, 图片附件) ├─→ WRPageHyperlinksAttachment (超链接) ├─→ WRPageChapterToolAttachment (章节工具) ├─→ WRPageFlyleafAttachment (扉页) ├─→ WRPageCodeView (代码块) ├─→ WRPageVideoView (视频) └─→ WRPageTableAttachment (表格) ``` --- ## 二、核心业务数据流图 (DFD) ### 2.1 EPUB 内容数据流 (服务器 → 屏幕像素) ``` ┌──────────┐ HTTPS ┌──────────────────────────────────────────────────┐ │ 微信读书 │─────────────→│ ① WRBookNetwork.loadTarForEpubBookId:chapter: │ │ 服务器 │ 加密 ZIP │ isPreload: │ │ │ {bookId}_ │ ↓ │ │ │ DECRYPT.zip │ ② WRBookNetwork.handleUnzipWithBookId: │ │ │ │ zipPath:encryptKey:plainBookDirectory: │ │ │ │ ↓ │ │ │ │ ③ WREncryptedFileManager │ │ │ │ decryptContentsOfFile:forBookId:isFileLost: │ │ │ │ 密钥来源: keyForBookId: (Keychain) │ │ │ │ ↓ 解密后 XHTML 文件 │ │ │ │ │ │ │ │ ④ WRBookNetwork.fileContentWithChapter: │ │ │ │ book:shouldRemoveHtmlTags:filterTranslate: │ │ │ │ ↓ 读取 XHTML 内容 │ │ │ │ │ │ │ │ ⑤ WREpubTypesetter.attributeStringWithFilePath: │ │ │ │ ↓ CSS 级联 (5层) │ │ │ │ ↓ DTHTMLAttributedStringBuilder._buildString │ │ │ │ ↓ DTHTMLParser (SAX) → DTHTMLElement (DOM) │ │ │ │ ↓ DTHTMLElement.attributedString │ │ │ │ ↓ 输出: NSAttributedString │ │ │ │ │ │ │ │ ⑥ WRCoreTextLayouter (CTTypesetter 分页) │ │ │ │ ↓ CTTypesetterSuggestLineBreak (逐行排版) │ │ │ │ ↓ 每页一个 CTFrame │ │ │ │ ↓ WRChapterPageCount (NSRange 映射) │ │ │ │ │ │ │ │ ⑦ WRCoreTextLayoutFrame (单页排版帧) │ │ │ │ ↓ CTFrameGetLines → CTLine 列表 │ │ │ │ ↓ avoidPageBreakInside (截断保护) │ │ │ │ │ │ │ │ ⑧ WRPageView.drawRect: │ │ │ │ CGContextTranslateCTM (坐标翻转) │ │ │ │ CTFrameDraw (绘制文字) │ │ │ │ 绘制图片/高亮/下划线/选择 │ │ │ │ ↓ │ └──────────┘ └──────────────────────────────────────────────────┘ ↓ 屏幕像素渲染 ``` ### 2.2 加密密钥数据流 (DRM 保护链) ``` ┌─────────────────────────────────────────────────────────────────────────┐ │ DRM 密钥生命周期 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────┐ │ │ │ 服务器 │─── encryptKey (每章独立) ───┐ │ │ └──────────┘ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ WRPreloadBookManager │ │ │ │ saveEncryptKey:forPath:bookId: │ │ │ │ ↓ 存储到: NSUserDefaults (临时) │ │ │ │ encryptKeyForPath:bookId: (读取) │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ WREncryptedFileManager │ │ │ │ keyForBookId: │ │ │ │ ↓ 优先级: Keychain > NSUserDefaults > 内存缓存 │ │ │ │ │ │ │ │ decryptContentsOfFile:forBookId:isFileLost: │ │ │ │ ↓ 算法: AES-128-CBC │ │ │ │ ↓ 输入: 加密的 XHTML 数据 │ │ │ │ ↓ 输出: 明文 XHTML │ │ │ │ ↓ 检测: "REWD" 自定义头部标识 │ │ │ │ │ │ │ │ encryptFileForBookId:originalEncryptKey:atPath:toPath: │ │ │ │ ↓ 解密后立即重新加密存储 (本地 DRM) │ │ │ │ ↓ 防止直接拷贝文件读取 │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ 本地存储 │ │ │ │ Documents/{bookId}/plainBookDirectory/ (解密后的 EPUB) │ │ │ │ Documents/{bookId}/epubImage/ (书籍图片缓存) │ │ │ │ Library/{cachePath}/epubImage/ (SDWebImage 缓存) │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ 密钥安全机制: │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ 1. 传输层: HTTPS + 加密 ZIP │ │ │ │ 2. 解密层: 逐章解密 (WREncryptedFileManager) │ │ │ │ 3. 存储层: 本地二次加密 (encryptFileForBookId) │ │ │ │ 4. 密钥层: 每本书独立密钥 + Keychain 安全存储 │ │ │ │ 5. 设备层: 密钥与设备绑定 │ │ │ │ 6. 试读层: freeTrialChapterCutOffStringLocaion (服务端控制) │ │ │ └─────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────────┘ ``` ### 2.3 用户标注数据流 ``` ┌─────────────────────────────────────────────────────────────────────┐ │ 用户触摸/长按 │ │ │ │ │ ▼ │ │ WRPageView 手势识别 (UITapGestureRecognizer / UILongPressGestureRecognizer) │ │ │ │ ▼ │ │ CoreText Hit Test │ │ touchPoint = WRSFlipPointForCoreText(touch, viewHeight) // 坐标翻转 │ CTLineGetStringIndexForPosition(line, touchPoint) // 坐标→字符索引 │ CTLineGetOffsetForStringIndex(line, charIndex, NULL) // 字符索引→坐标 │ │ │ │ ▼ │ │ 选择范围确定 (selectionStartIndex .. selectionEndIndex) │ │ │ │ │ ▼ │ │ UIMenuController 弹出操作菜单 │ │ ├─ 划线/高亮 ─→ WRChapterData.addHighlightInRange:key:itemId:color:│ │ ├─ 写想法 ─→ WRBookNetwork.addReview:shareToWechat:... │ │ ├─ 复制 ─→ UIPasteboard │ │ ├─ 查询/翻译 ─→ WRReaderDictionaryViewController │ │ └─ 分享 ─→ WRShareManager │ │ │ │ │ ▼ │ │ WRChapterData 标注存储 │ │ NSAttributedString 注入自定义属性: │ │ "com.weread.highlight" → 高亮数据 │ │ "com.weread.underline" → 下划线数据 │ │ │ │ │ ▼ │ │ 服务器同步 │ │ WRBookNetwork.addReview:shareToWechat:audioArticleId:... │ │ WRBookNetwork.loadBookmarkListWithBookId:syncKey:callback: │ │ │ │ │ ▼ │ │ 重新排版 │ │ WRReaderViewController.recomposeCurrentPageViewWithSource: │ │ → WRPageView.setNeedsDisplay → drawRect: 重绘 │ └─────────────────────────────────────────────────────────────────────┘ ``` ### 2.4 阅读进度数据流 ``` ┌─────────────────────────────────────────────────────────────────────┐ │ 本地进度持久化 │ │ WRReaderViewController._saveReadingProgressAndIsAsync: │ │ ↓ NSUserDefaults 存储: │ │ - bookId / chapterUid / chapterIdx │ │ - filePosition / stringLocation │ │ - pageOfChapter / pageOfFlyleaf │ │ - lastReadDate / readingTime │ │ │ │ │ ▼ │ │ 云端同步 │ │ WRBookNetwork.uploadBookProgressAndReadingTime: │ │ ↓ POST /api/book/progress │ │ ↓ 参数: bookId, chapterUid, progress, readingTime │ │ │ │ │ ▼ │ │ 启动恢复 │ │ WRReaderViewController.initWithBook:progress:... │ │ ↓ loadBookReadInfoWithBookId: (从服务器获取最新进度) │ │ ↓ gotoChapterIdx:position:positionOfFile: (跳转到上次位置) │ │ ↓ scrollToLastReadingProgressIfNeeded │ └─────────────────────────────────────────────────────────────────────┘ ``` ### 2.5 预加载数据流 ``` ┌─────────────────────────────────────────────────────────────────────┐ │ 预测触发 │ │ WRForecastUtils.shouldPreloadChapterUidsForBook: │ │ type:bookRank:archiveRank: │ │ ↓ 根据书籍排名 + 用户行为预测 │ │ │ │ │ ▼ │ │ 预加载执行 │ │ WRChapterDownloadManger._preloadChapterContentWithBook: │ │ type:bookRank: │ │ ↓ 后台线程下载相邻章节 │ │ │ │ │ ▼ │ │ 下载 + 解密 + 缓存 │ │ WRBookNetwork.loadTarForEpubBookId:chapters:isPreload: │ │ → handleUnzipWithBookId → WREncryptedFileManager │ │ → 存储到 Documents/{bookId}/plainBookDirectory/ │ │ │ │ │ ▼ │ │ 图片预缓存 │ │ WRBookNetwork.storeTarEpubImageToDiskWithBookId: │ │ chapterUid:untarDirectory: │ │ → SDWebImage → com.hackemist.SDWebImageCache.epubImage │ │ │ │ │ ▼ │ │ 密钥预缓存 │ │ WRPreloadBookManager.saveEncryptKey:forPath:bookId: │ │ → NSUserDefaults / Keychain │ └─────────────────────────────────────────────────────────────────────┘ ``` ### 2.6 JS Bridge 数据流 (WebView 路径 — 公众号/文集文章) ``` ┌─────────────────────────────────────────────────────────────────────┐ │ JS → 原生 │ │ window.webkit.messageHandlers.MPReader.postMessage(data) │ │ wereadBridge.execMPReaderMethod('MPReader', data) │ │ ↓ WKScriptMessageHandler │ │ ↓ WRMPReadingViewModel 解析 JSON │ │ │ │ │ ▼ │ │ 原生 → JS │ │ [webView evaluateJavaScript:@"..." completionHandler:nil] │ │ ↓ WKUserScript 注入脚本 │ │ │ │ │ ▼ │ │ 高亮系统 (weread-highlighter.js) │ │ rangy.init() → 创建 Highlighter (TextRange 模式) │ │ 注册 ClassApplier: "highlight"/"review"/"reference"/"tts" │ │ 监听 selectionchange 事件 │ │ │ │ │ ▼ │ │ 通信协议: wereadapijs://dispatch_message/ │ │ URL Scheme 编码消息, iframe 触发 │ └─────────────────────────────────────────────────────────────────────┘ ```