docs: add iOS FFI integration guide (DOC-FULL-027)
- Architecture: Swift→uniffi C-ABI→Rust FFI→core - Type mapping: Rust struct/enum ↔ Swift Record/Enum - Core API reference: document parsing, search, anchors, V2 sessions - Error handling, thread safety, performance recommendations Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
433b1ad7c0
commit
4badf86953
221
docs/ios-ffi-integration-guide.md
Normal file
221
docs/ios-ffi-integration-guide.md
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
# Document Runtime iOS FFI 调用指南
|
||||||
|
|
||||||
|
## 1. 概述
|
||||||
|
|
||||||
|
Document Runtime(zx_document_core)通过 UniFFI 生成 C-ABI 兼容的 Swift 绑定,嵌入 iOS 应用作为 `ZxDocumentRuntime.xcframework`。
|
||||||
|
|
||||||
|
Swift 层通过 uniffi 生成的 `zx_document.swift` 调用所有 Rust 功能。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 架构
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────┐
|
||||||
|
│ iOS Swift │
|
||||||
|
│ zx_document.swift (uniffi 生成) │
|
||||||
|
│ ├─ buildDocumentInfo() │
|
||||||
|
│ ├─ parseMarkdown() │
|
||||||
|
│ ├─ extractPdfText() │
|
||||||
|
│ ├─ extractEpubChapterTexts() │
|
||||||
|
│ ├─ searchBlocks() / searchText() │
|
||||||
|
│ ├─ resolveNoteAnchors() │
|
||||||
|
│ ├─ startReadingSessionV2() │
|
||||||
|
│ ├─ pushHeartbeatV2() │
|
||||||
|
│ ├─ exportPending() / ackExported() │
|
||||||
|
│ └─ ... │
|
||||||
|
└──────────────┬───────────────────────┘
|
||||||
|
│ C-ABI (uniffi)
|
||||||
|
┌──────────────▼───────────────────────┐
|
||||||
|
│ Rust: zx_document_ffi │
|
||||||
|
│ lib.rs — #[uniffi::export] fns │
|
||||||
|
└──────────────┬───────────────────────┘
|
||||||
|
│
|
||||||
|
┌──────────────▼───────────────────────┐
|
||||||
|
│ Rust: zx_document_core │
|
||||||
|
│ document / markdown / pdf / epub / │
|
||||||
|
│ search / anchors / events_v2 / ... │
|
||||||
|
└──────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 类型映射
|
||||||
|
|
||||||
|
### 3.1 基础类型
|
||||||
|
|
||||||
|
| Rust | Swift |
|
||||||
|
|------|-------|
|
||||||
|
| `String` | `String` |
|
||||||
|
| `u32` / `u64` | `UInt32` / `UInt64` |
|
||||||
|
| `i64` | `Int64` |
|
||||||
|
| `bool` | `Bool` |
|
||||||
|
| `Vec<T>` | `[T]` |
|
||||||
|
| `Option<T>` | `T?` |
|
||||||
|
| `Result<T, E>` | `throws` |
|
||||||
|
|
||||||
|
### 3.2 结构体 → Record
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(uniffi::Record)]
|
||||||
|
pub struct DocumentInfo { ... }
|
||||||
|
```
|
||||||
|
```swift
|
||||||
|
// uniffi 生成
|
||||||
|
public struct DocumentInfo {
|
||||||
|
public var materialId: String
|
||||||
|
public var title: String
|
||||||
|
public var materialType: MaterialType
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 枚举 → Enum
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(uniffi::Enum)]
|
||||||
|
pub enum MaterialType { Markdown, Text, Pdf, Image, Epub, Word, Excel, PowerPoint, Unknown }
|
||||||
|
```
|
||||||
|
```swift
|
||||||
|
public enum MaterialType {
|
||||||
|
case markdown, text, pdf, image, epub, word, excel, powerPoint, unknown
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 核心 API
|
||||||
|
|
||||||
|
### 4.1 文档解析
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// 构建文档信息(文件类型检测 + 元数据提取)
|
||||||
|
let info = try buildDocumentInfo(
|
||||||
|
filePath: "/path/to/file.md",
|
||||||
|
materialId: "mat-001",
|
||||||
|
title: "My Document"
|
||||||
|
)
|
||||||
|
// → DocumentInfo { materialType, pageCount, wordCount, epubChapterCount, ... }
|
||||||
|
|
||||||
|
// Markdown 解析为块
|
||||||
|
let blocks = try parseMarkdown(markdown: "# Title\n\nContent")
|
||||||
|
// → [DocumentBlock]
|
||||||
|
|
||||||
|
// PDF 元数据
|
||||||
|
let pdfMeta = try readPdfMetadata(filePath: "/path/to/book.pdf")
|
||||||
|
// → PdfMetadata { pageCount, title, author }
|
||||||
|
|
||||||
|
// PDF 文本提取
|
||||||
|
let pages = try extractPdfText(filePath: "/path/to/book.pdf")
|
||||||
|
// → [PdfPageText { pageNumber, text }]
|
||||||
|
|
||||||
|
// EPUB 元数据
|
||||||
|
let epubMeta = try readEpubMetadata(filePath: "/path/to/book.epub")
|
||||||
|
// → EpubMetadata { title, author, chapterCount }
|
||||||
|
|
||||||
|
// EPUB 章节文本提取(HTML strip)
|
||||||
|
let chapters = try extractEpubChapterTexts(filePath: "/path/to/book.epub")
|
||||||
|
// → [EpubChapterText { chapterId, text }]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 搜索
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// 搜索 Markdown 块
|
||||||
|
let results = searchBlocks(blocks: blocks, query: "keyword")
|
||||||
|
// → [SearchResult { blockId, snippet, matchStart, matchEnd }]
|
||||||
|
|
||||||
|
// 搜索纯文本
|
||||||
|
let textResults = searchText(content: text, query: "keyword")
|
||||||
|
// → [SearchResult { lineNumber, ... }]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 锚点
|
||||||
|
|
||||||
|
```swift
|
||||||
|
let anchors = try resolveNoteAnchors(
|
||||||
|
blocks: blocks,
|
||||||
|
anchorHints: ["#heading-1", "#paragraph-2"]
|
||||||
|
)
|
||||||
|
// → [NoteAnchor { anchorId, blockId, ... }]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.4 V2 阅读会话
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// 开始会话
|
||||||
|
let sessionId = try startReadingSessionV2(
|
||||||
|
material: ReadingMaterialRefV2(materialId: "mat-001"),
|
||||||
|
timestampMs: Int64(Date().timeIntervalSince1970 * 1000)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 推送位置变更事件
|
||||||
|
try pushPositionChangedV2(
|
||||||
|
sessionId: sessionId,
|
||||||
|
position: ReadingPosition.markdown(
|
||||||
|
blockId: "b1",
|
||||||
|
scrollProgress: 0.5
|
||||||
|
),
|
||||||
|
timestampMs: nowMs()
|
||||||
|
)
|
||||||
|
|
||||||
|
// 导出待上传事件
|
||||||
|
let (events, state) = exportPendingV2()
|
||||||
|
// → ([BufferedReadingEventV2], EventBufferStateV2)
|
||||||
|
|
||||||
|
// ACK 已上传事件
|
||||||
|
ackExportedV2(count: UInt32(events.count))
|
||||||
|
|
||||||
|
// 关闭会话
|
||||||
|
try closeReadingSessionV2(sessionId: sessionId, timestampMs: nowMs())
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 错误处理
|
||||||
|
|
||||||
|
所有 FFI 函数返回 `Result<T, String>`(UniFFI 映射为 Swift `throws`):
|
||||||
|
|
||||||
|
```swift
|
||||||
|
do {
|
||||||
|
let info = try buildDocumentInfo(filePath: path, materialId: id, title: title)
|
||||||
|
// 使用 info
|
||||||
|
} catch {
|
||||||
|
print("Document Runtime error: \(error)")
|
||||||
|
// error 是 Rust DocumentError 的 Display 字符串
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
常见错误:
|
||||||
|
- `IoError` — 文件不存在/无权限
|
||||||
|
- `ParseError` — 格式损坏/不支持的格式
|
||||||
|
- `MarkdownParseError` — Markdown 语法错误
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 线程安全
|
||||||
|
|
||||||
|
- Rust 层使用 `Mutex<Vec<T>>` 保护全局状态(EventBuffer)
|
||||||
|
- 所有 FFI 函数可从任意线程调用
|
||||||
|
- Swift 层应在主线程调用 UI 更新,后台线程调用 FFI
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 性能建议
|
||||||
|
|
||||||
|
| 操作 | 建议 |
|
||||||
|
|------|------|
|
||||||
|
| `buildDocumentInfo` | 主线程调用(< 50ms),缓存结果 |
|
||||||
|
| `extractPdfText` | 后台线程(可能耗时数秒) |
|
||||||
|
| `parseMarkdown` | 任意线程(< 10ms) |
|
||||||
|
| `searchBlocks` | 任意线程(< 10ms) |
|
||||||
|
| `exportPendingV2` | 后台定时器(30s),主线程调用也可 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 相关文档
|
||||||
|
|
||||||
|
- [V2 事件 Pipeline 架构](../ios-projects/docs/v2-event-pipeline.md)(ios-projects 仓库)
|
||||||
|
- [Rust FFI 绑定更新流程](../ios-projects/docs/rust-ffi-update.md)(ios-projects 仓库)
|
||||||
|
- [UniFFI UDL 编写规范](./uniffi-udl-spec.md)
|
||||||
|
- [文件格式支持矩阵](./file-format-support-matrix.md)
|
||||||
Loading…
x
Reference in New Issue
Block a user