
简介这是一份面向iOS/macOS原生开发者的技术资源包提供Couchbase Lite嵌入式NoSQL数据库的完整本地实现专为解决移动应用离线数据存储与跨设备实时同步难题而设计。资源适用于需构建高可靠性同步能力的应用场景如即时通讯、移动办公及物联网终端等尤其适合具备Swift/Objective-C开发经验的中高级工程师快速集成文档型本地数据库。压缩包共623个文件涵盖183个头文件.h、126个Swift源码、121个Objective-C实现.m及41个混合代码文件.mm辅以xcconfig配置、xcscheme工程定义、shell脚本与证书.cer/.der/.p12等关键构建要素整体体积仅4.19MB结构清晰、开箱即用。目前已有34人学习下载读者可直接获取完整的数据库引擎源码、同步协议实现、本地查询模块及配套证书体系无需额外编译即可理解其与Couchbase Server协同工作的核心机制。1. 项目概述为什么我们需要一个“口袋里的”数据库如果你是一名移动端或桌面端应用的开发者尤其是需要处理离线场景、复杂数据模型和跨设备同步的那么你一定对本地数据存储的复杂性深有体会。Core Data 太重SQLite 写起来繁琐UserDefaults 又太简陋。这时候一个轻量级、嵌入式、支持文档模型并能自动同步的数据库就成了刚需。Couchbase Lite 正是为此而生。简单来说Couchbase Lite 是一个专为移动和边缘设备设计的嵌入式 NoSQL 数据库引擎。它不是一个需要独立安装的服务而是一个可以直接打包进你的 iOS、macOS、Android 甚至 Windows 应用里的库。它的核心价值在于两点第一提供了灵活、高性能的 JSON 文档存储与查询能力第二内置了强大的数据同步协议可以轻松地与远程的 Couchbase Server 或云服务进行双向数据同步。这意味着你的应用可以无缝地在离线与在线状态间切换数据在后台自动处理冲突和合并为用户提供始终如一的体验。我最初接触它是在开发一个户外作业的巡检应用时。工人们在地下室或偏远山区网络时有时无但他们采集的表格、照片、地理位置信息必须实时保存并在有网时自动上传到中心服务器同时接收最新的任务单。Couchbase Lite 的“同步网关”Sync Gateway模式完美解决了这个痛点。它不像传统的 REST API 需要自己处理队列、重试和冲突解决而是将数据库级别的同步抽象成了一个简单的配置。对于需要构建响应式、离线优先应用如协作编辑、物联网数据采集、零售业库存管理的开发者来说这无疑是一个利器。2. 核心架构与设计哲学拆解2.1 文档模型与 SQLite 的思维转换Couchbase Lite 的核心数据模型是 JSON 文档。这和我们熟悉的 SQLite 关系型模型有根本区别。在 SQLite 里我们设计表结构定义字段类型通过外键关联数据。而在 Couchbase Lite 中一个“文档”就是一个自包含的 JSON 对象它拥有一个唯一的 ID文档内部可以嵌套数组和子文档。这种设计带来了巨大的灵活性。假设我们要存储一个“订单”数据。在 SQL 中我们可能需要orders、order_items、products等多张表。在 Couchbase Lite 中一个订单文档就可以包含所有信息{ “id”: “order_12345”, “type”: “order”, “customerId”: “cust_001”, “date”: “2023-10-27T10:30:00Z”, “items”: [ { “productId”: “prod_1”, “name”: “T-Shirt”, “quantity”: 2, “price”: 25.99 }, { “productId”: “prod_2”, “name”: “Mug”, “quantity”: 1, “price”: 12.50 } ], “totalAmount”: 64.48, “shippingAddress”: { “street”: “123 Main St”, “city”: “Anytown”, “zip”: “12345” } }这种“反规范化”的存储方式非常适合移动端一次读取就能渲染整个视图的场景避免了复杂的联表查询。但这也要求开发者转变思维从思考“如何设计规范的表结构”转变为“如何设计高效、自包含的文档结构”。注意灵活性不等于随意性。文档结构仍然需要精心设计。过度嵌套或文档过大超过几十KB会影响查询和同步性能。一个好的实践是将频繁独立访问的实体如用户资料和可能无限增长的数据如聊天记录拆分成不同的文档通过文档ID进行逻辑关联。2.2 数据同步的核心Couchbase 同步协议Couchbase Lite 最吸引人的特性莫过于其开箱即用的数据同步能力。这背后是 Couchbase 自定义的同步协议它基于 WebSocket 或 HTTP 长轮询实现了一个多主复制的模型。它的工作流程可以这样理解变更追踪本地数据库的任何增删改操作都会被记录到一个“修订历史”中。每个文档的每次更改都会生成一个唯一的revision ID。推送 (Push)当网络可用时Couchbase Lite 会将本地的变更集合一个修订列表打包发送给远端的同步端点通常是 Sync Gateway。拉取 (Pull)同时它也会从同步端点拉取其他设备或服务器上发生的、自己尚未拥有的变更。冲突解决如果同一个文档在不同端被同时修改就会产生冲突。Sync Gateway 或客户端可以配置冲突解决策略如“服务端获胜”、“客户端获胜”或者执行自定义的合并逻辑例如合并两个文档的特定字段。这个模型的美妙之处在于它对应用层是透明的。开发者只需要配置同步的方向持续推送、持续拉取、一次性拉取和过滤条件同步哪些通道或文档ID剩下的网络重试、数据压缩、增量传输等脏活累活都由 SDK 自动完成。2.3 与生态的集成Sync Gateway 与 Couchbase ServerCouchbase Lite 通常不直接与 Couchbase Server 集群对话中间有一个关键组件Sync Gateway。你可以把 Sync Gateway 看作一个智能的同步代理和流量控制器。身份验证与授权Sync Gateway 可以集成各种认证提供商如自带的用户/密码、OAuth2、OpenID Connect。更重要的是它引入了“通道”的概念。每个文档可以被分配到一个或多个通道每个用户可以被授予访问特定通道的权限。这天然地实现了数据的分区与多租户隔离。例如公司A的用户只能访问“channel_company_a”通道里的文档。数据路由与过滤Sync Gateway 根据用户的通道权限只同步该用户有权访问的文档给对应的 Couchbase Lite 客户端。这极大地减少了移动设备上不必要的数据传输和存储。函数钩子Sync Gateway 允许你编写 JavaScript 函数在文档同步前sync function或同步后post-update执行逻辑用于验证数据、计算衍生字段、触发外部服务等。而 Couchbase Server 则作为整个系统的“单一数据源”负责海量数据的持久化、集群管理和高并发在线查询。这样一个三层架构移动端 Couchbase Lite - Sync Gateway - Couchbase Server构成了一个完整、可扩展的离线优先应用后端。3. 从零开始在 iOS/macOS 项目中集成与基础操作3.1 环境配置与数据库初始化集成 Couchbase Lite 非常直接。对于 Swift Package Manager你只需要在 Xcode 中添加其 GitHub 仓库的 URL。CocoaPods 和 Carthage 也同样支持。安装完成后第一步是初始化数据库。这里有几个关键参数需要理解import CouchbaseLiteSwift // 1. 初始化数据库配置 var config DatabaseConfiguration() // 指定数据库目录默认为应用的 Documents 目录 if let dir FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first { config.directory dir.path } // 2. 创建或打开数据库 let database: Database do { database try Database(name: “myapp”, config: config) } catch { print(“无法打开数据库: \(error)”) return }数据库目录强烈建议显式设置一个目录。默认目录在 iOS 上可能受 iCloud 备份影响且在某些情况下如 App Groups 共享需要指定到共享容器内。数据库名称名称中避免使用特殊字符它最终会对应一个文件夹下的.cblite2目录。实操心得在真实项目中我会将数据库实例包装在一个单例或依赖注入容器中管理。同时考虑到数据库文件可能增长要确保你的应用有相应的数据清理或归档策略特别是对于同步了大量历史数据的场景。3.2 文档的 CRUD 操作详解让我们看看如何操作一个文档。创建/更新文档 文档以MutableDocument对象的形式存在你可以像字典一样操作它。// 创建新文档 let newTask MutableDocument() newTask.setString(“task”, forKey: “type”) newTask.setString(“Buy groceries”, forKey: “title”) newTask.setBoolean(false, forKey: “completed”) newTask.setDate(Date(), forKey: “createdAt”) // 可以设置复杂的嵌套值 newTask.setArray([“milk”, “eggs”, “bread”], forKey: “items”) // 保存到数据库如果文档ID不存在则创建存在则更新 try database.saveDocument(newTask) // 更新现有文档 if let existingDoc database.document(withID: newTask.id)?.toMutable() { existingDoc.setBoolean(true, forKey: “completed”) try database.saveDocument(existingDoc) }读取文档 通过 ID 获取文档是最快的方式。if let document database.document(withID: “some_doc_id”) { let title document.string(forKey: “title”) // 安全获取 String 类型值 let items document.array(forKey: “items”)?.toArray() as? [String] // 整个文档可以转为 Dictionary let dict document.toDictionary() }删除文档 删除操作也会产生一个新的修订版本以便在同步时告知其他节点此文档已被删除。if let doc database.document(withID: “doc_to_delete”) { try database.deleteDocument(doc) } // 或者使用 purge 彻底清除不进入同步流不可恢复 try database.purgeDocument(withID: “doc_to_purge”)3.3 查询使用 QueryBuilder 与 N1QLCouchbase Lite 提供了两种主要的查询方式QueryBuilder API类型安全、流畅接口和N1QLSQL for JSON。对于 iOS/macOS 开发QueryBuilder 更符合 Swift 的习惯。假设我们要查询所有未完成的任务并按创建时间排序import CouchbaseLiteSwift let query QueryBuilder .select(SelectResult.all()) // 选择所有属性 .from(DataSource.database(database)) .where( Expression.property(“type”).equalTo(Expression.string(“task”)) .and(Expression.property(“completed”).equalTo(Expression.boolean(false))) ) .orderBy(Ordering.property(“createdAt”).ascending()) do { let results try query.execute() for result in results { // result 是一个 DictionaryObject if let dict result.toDictionary() { print(“Task: \(dict[“title”] ?? “”)”) } // 或者通过 key 直接访问 let title result.string(forKey: “title”) } } catch { print(“查询失败: \(error)”) }索引优化对于频繁查询的字段创建索引能大幅提升性能。例如为type和completed创建复合索引let index IndexBuilder.valueIndex(items: [ ValueIndexItem.expression(Expression.property(“type”)), ValueIndexItem.expression(Expression.property(“completed”)) ]) try database.createIndex(index, withName: “idx_type_completed”)注意事项索引会加快查询但会增加写操作的开销和数据库文件大小。只为最关键的查询路径创建索引。对于全文搜索Couchbase Lite 还支持功能强大的全文索引可以高效地在文本字段中搜索单词。4. 实现数据同步配置、监听与冲突处理4.1 配置同步端点与复制器同步的核心是Replicator对象。你需要一个同步端点 URL指向 Sync Gateway和一个有效的认证方式。// 1. 定义同步目标这里以本地 Sync Gateway 为例 let targetEndpoint URLEndpoint(url: URL(string: “ws://localhost:4984/mydb”)!) // WebSocket 协议 // 或 URLEndpoint(url: URL(string: “http://localhost:4984/mydb”)!) // HTTP 协议 // 2. 配置复制器 var config ReplicatorConfiguration(database: database, target: targetEndpoint) config.replicatorType .pushAndPull // 双向同步 config.continuous true // 持续同步长连接false 则为一次性同步 // 3. 配置通道过滤可选通常由 Sync Gateway 的 sync function 控制 // config.channels [“channel_user_\(userId)”] // 4. 配置身份验证这里使用基本认证 config.authenticator BasicAuthenticator(username: “username”, password: “password”) // 5. 创建并启动复制器 let replicator Replicator(config: config) // 添加状态变更监听器 let token replicator.addChangeListener { change in let status change.status print(“同步器状态: \(status.activity) - \(status.progress.completed)/\(status.progress.total)”) if let error status.error { print(“同步错误: \(error)”) } } replicator.start()关键参数解析replicatorType:.push仅上传、.pull仅下载或.pushAndPull双向。continuous: 设置为true时复制器会建立一个持久连接实时监听变更。这是实现“实时同步”体验的关键。对于只需要偶尔同步如手动刷新的场景可以设为false然后在需要时调用start()。authenticator: 支持BasicAuthenticator、SessionAuthenticator用于 Cookie 或 Token 认证等。生产环境通常使用基于 Token 的认证。4.2 监听同步进度与网络状态复制器的状态监听至关重要它让你能在 UI 上展示同步状态如“正在同步...”或“同步失败”。replicator.addChangeListener { change in let status change.status switch status.activity { case .stopped: print(“同步已停止”) // 可能是用户登出或手动停止 case .offline: print(“网络离线”) // 可以在这里提示用户检查网络 case .connecting: print(“正在连接同步网关...”) case .idle: print(“同步空闲所有变更已处理完毕”) // 这是一个很好的时机更新 UI通知用户数据已最新 case .busy: print(“同步进行中已处理 \(status.progress.completed)/\(status.progress.total)”) // 可以更新进度条 } if let error status.error { // 处理错误例如认证失败、网络超时等 handleSyncError(error) } }在实际应用中我通常会将这些状态绑定到 ViewModel 的Published属性上从而驱动 UI 的状态更新如显示一个小的同步指示器。4.3 处理同步冲突的策略冲突是分布式系统的常态。Couchbase Lite 提供了自动和手动两种解决方式。自动解决策略在ReplicatorConfiguration中设置。config.conflictResolver ConflictResolver.default // 默认服务端获胜 // 或 config.conflictResolver ConflictResolver.localWins // 本地获胜这适用于简单的、对数据一致性要求不苛刻的场景。自定义冲突解决对于业务逻辑复杂的冲突如合并购物车商品你需要实现自己的ConflictResolver。class CustomConflictResolver: ConflictResolver { func resolve(conflict: Conflict) - Document? { // conflict.remoteDocument: 远程服务器上的版本 // conflict.localDocument: 设备本地的版本 // conflict.baseDocument: 冲突发生前的共同祖先版本可能为nil guard let localDoc conflict.localDocument?.toMutable(), let remoteDoc conflict.remoteDocument else { // 如果一方文档被删除可以决定返回另一方或nil return conflict.remoteDocument ?? conflict.localDocument } // 示例合并“items”数组去重 let localItems localDoc.array(forKey: “items”)?.toArray() as? [String] ?? [] let remoteItems remoteDoc.array(forKey: “items”)?.toArray() as? [String] ?? [] let mergedItems Array(Set(localItems remoteItems)) localDoc.setArray(mergedItems, forKey: “items”) // 可以选择保留最新的时间戳 let latestDate max(localDoc.date(forKey: “updatedAt”) ?? Date.distantPast, remoteDoc.date(forKey: “updatedAt”) ?? Date.distantPast) localDoc.setDate(latestDate, forKey: “updatedAt”) return localDoc } } // 应用自定义解析器 config.conflictResolver CustomConflictResolver()重要提示冲突解决逻辑必须保证幂等性和确定性。即给定相同的本地、远程和基础文档无论运行多少次结果都应该相同。复杂的冲突解决最好在 Sync Gateway 的sync function中实现以保证所有客户端遵循同一套规则。5. 高级特性与性能优化实战5.1 使用预构建数据库加速首次启动对于包含大量初始数据如产品目录、城市列表的应用将数据打包在应用内首次启动时直接加载比通过网络同步要快得多。这可以通过“预构建数据库”实现。在开发环境准备数据在你的桌面或服务器上使用 Couchbase Lite 的 .NET 或 Java 版本创建一个数据库并导入所有初始数据。打包数据库文件将生成的.cblite2目录整个压缩放入应用的资源包Asset Catalog 或 Bundle中。应用首次启动时复制func setupDatabase() throws - Database { let dbName “myapp” let finalDBPath // ... 最终数据库路径 if !Database.exists(withName: dbName, inDirectory: finalDBPath) { // 从应用包中复制预构建数据库 guard let prebuiltDBURL Bundle.main.url(forResource: “prebuilt”, withExtension: “cblite2”) else { throw NSError(domain: “AppError”, code: -1, userInfo: [NSLocalizedDescriptionKey: “预构建数据库未找到”]) } try Database.copy(from: prebuiltDBURL, toDatabase: “myapp”, withConfig: nil) } // 打开数据库 return try Database(name: dbName) }5.2 数据库加密保障数据安全移动设备可能丢失或被盗对本地数据库加密是保护用户敏感信息的必要措施。Couchbase Lite 支持使用 SQLCipher 进行 AES-256 加密。import CouchbaseLiteSwift var config DatabaseConfiguration() // ... 设置目录 // 创建或提供加密密钥务必安全存储例如使用钥匙串 let encryptionKey EncryptionKey.password(“YourStrongPassword!”) config.encryptionKey encryptionKey let database try Database(name: “secureDB”, config: config)密钥管理要点切勿将密钥硬编码在代码中。对于 iOS/macOS使用系统的Keychain Services来安全地生成、存储和检索加密密钥。如果用户有密码可以考虑基于用户密码派生密钥。但要注意一旦密钥丢失数据库将永远无法打开。你可以在数据库创建后随时启用、禁用或更改加密密钥通过Database.changeEncryptionKey(_:)方法。5.3 监听数据变更与驱动 UI 更新Couchbase Lite 提供了高效的变更监听 API让你可以轻松实现响应式 UI。监听单个文档let docId “task_123” let token database.addDocumentChangeListener(id: docId) { change in print(“文档 \(change.documentID) 发生了变更”) if change.document ! nil { // 文档存在被创建或更新 } else { // 文档被删除 } } // 记得在适当的时候移除监听器例如 deinit 中 // database.removeChangeListener(withToken: token)监听查询结果集LiveQuery 这非常强大相当于一个可观察的查询。当查询结果中的任何文档发生变化或新文档符合查询条件时监听器都会被触发。let query QueryBuilder .select(SelectResult.expression(Meta.id)) .from(DataSource.database(database)) .where(Expression.property(“type”).equalTo(Expression.string(“task”))) let token query.addChangeListener { change in guard let results change.results else { return } print(“查询结果集更新了共有 \(results.count) 个任务”) // 在这里刷新你的 UITableView 或 SwiftUI List self?.updateUI(with: results) } query.execute() // 开始监听在 SwiftUI 中你可以将LiveQuery的结果包装成一个Published属性实现数据库到 UI 的自动绑定。5.4 性能调优与最佳实践批量操作进行大量文档写入时使用Database.inBatch(_:)可以显著提升性能因为它将多次磁盘 I/O 合并为一次事务。try database.inBatch { for item in largeItemArray { let doc MutableDocument() // ... 设置数据 try database.saveDocument(doc) } }控制文档大小避免创建过大的单个文档如将整个相册的 Base64 图片数据存为一个字段。大文档会影响查询、同步和内存使用。应将大块二进制数据BLOB存储在文件系统中而只在文档中保存文件的引用路径。明智地使用索引只为最频繁的查询创建索引。使用EXPLAIN语句通过Query.explain()分析查询计划判断是否使用了索引。同步调优心跳间隔在ReplicatorConfiguration中设置heartbeat间隔如 300 秒以在长时间无数据流动时保持 WebSocket 连接活跃避免被中间网络设备断开。重试逻辑复制器内置了指数退避的重试机制通常不需要手动处理。但你可以通过监听错误状态在特定错误如认证失败时采取不同策略。选择性同步利用 Sync Gateway 的通道和docIDs过滤器只同步用户需要的数据子集这对拥有海量数据的应用至关重要。6. 疑难杂症与故障排查实录即使设计得再完善在实际开发中也会遇到各种问题。下面是我和团队踩过的一些坑以及解决方案。6.1 常见错误与解决方案速查表问题现象可能原因排查步骤与解决方案数据库无法打开数据库文件损坏、加密密钥错误、目录权限不足。1. 检查DatabaseConfiguration.directory路径是否可写。2. 确认加密密钥与创建数据库时使用的一致。3. 尝试使用Database.exists检查文件是否存在。如果怀疑损坏尝试从备份恢复。同步器无法连接Sync Gateway 地址/端口错误、网络问题、SSL证书问题Android/iOS 对证书要求严格。1. 使用curl或浏览器测试 Sync Gateway 的/_all_docs端点是否可达。2. 检查设备网络代理设置。3. 对于自签名证书需要在ReplicatorConfiguration中设置acceptOnlySelfSignedServerCertificate为false仅限开发环境。生产环境必须使用有效证书。同步一直停留在“连接中”或“空闲”但无数据流动认证失败、用户无通道访问权限、Sync Gateway 的sync function过滤了所有文档。1. 查看 Sync Gateway 日志确认认证是否成功。2. 检查 Sync Gateway 配置中该用户是否被分配了正确的通道。3. 在 Sync Gateway 的sync function中添加console.log查看文档是否被正确路由。文档更新后同步未触发更改未保存、复制器未启动或未设置为持续同步、文档不在同步范围内。1. 确认database.saveDocument调用成功且无异常。2. 确认replicator.start()已被调用且continuous true。3. 检查文档的channels属性如果使用是否在用户权限内。应用崩溃日志提示CouchbaseLite相关错误多线程访问数据库违规、监听器未正确移除导致内存泄漏。1.Couchbase Lite 对象不是线程安全的。确保所有数据库操作、文档访问和查询执行都在同一个串行队列中进行。我通常会创建一个专用的DispatchQueue来管理所有数据库交互。2. 在视图控制器或对象销毁时使用removeChangeListener移除所有监听器。数据库文件体积增长过快未压缩的附件、过多的修订历史、未清理的已删除文档。1. 对于附件使用BlobAPI 并让 SDK 自动压缩。2. 考虑启用数据库的自动压缩功能Database.performMaintenance(type: .compact)但需在应用空闲时手动触发。3. 定期清理已删除的文档purge或使用DatabaseConfiguration的maxRevTreeDepth限制修订历史深度。6.2 调试与日志收集当问题难以定位时详细的日志是救命稻草。启用更详细的日志// 在应用启动早期调用 Database.log.console.domains .all // 输出所有域日志 Database.log.console.level .verbose // 设置为最详细的级别这会在 Xcode 控制台输出大量内部信息包括网络请求、SQL 语句等对排查同步和查询问题极有帮助。获取数据库诊断信息let diagnostics DatabaseDiagnostic(database: database) print(“数据库路径: \(diagnostics.path)”) print(“文档数量: \(diagnostics.count)”) print(“最后序列号: \(diagnostics.lastSequence)”) // 可以将其打包发送到你的错误分析平台网络抓包对于同步问题使用像Proxyman或Charles这样的工具抓取设备与 Sync Gateway 之间的 HTTP/WebSocket 流量可以清晰地看到握手、认证、数据推送/拉取的全过程是诊断网络层问题的终极手段。6.3 一个典型的内存管理陷阱一个常见的错误模式是在后台线程中捕获了数据库对象如查询结果然后在主线程中访问它。由于 Couchbase Lite 对象底层关联着 C 资源跨线程访问会导致未定义行为甚至崩溃。错误示例DispatchQueue.global(qos: .background).async { let results try? self.query.execute() DispatchQueue.main.async { // 危险results 是在后台线程创建的 ResultSet self.uiResults results // 可能导致后续访问时崩溃 } }正确做法要么将所有数据库操作封装到一个串行队列中要么在切换线程前将从数据库获取的数据转换为纯值类型如Array、Dictionary。DispatchQueue.global(qos: .background).async { let results try? self.query.execute() let dataArray results?.allResults().map { $0.toDictionary() } ?? [] // 转换为值类型 DispatchQueue.main.async { self.uiData dataArray // 安全 } }7. 项目实战构建一个离线优先的笔记应用理论说了这么多我们通过一个简化版的笔记应用来串联核心概念。这个应用允许用户创建、编辑、删除笔记并在所有设备间自动同步。7.1 数据模型设计我们设计一个简单的note文档。{ “id”: “note_001”, “type”: “note”, “title”: “购物清单”, “content”: “牛奶鸡蛋面包”, “createdAt”: “2023-10-27T10:00:00Z”, “updatedAt”: “2023-10-27T11:30:00Z”, “tags”: [“personal”, “shopping”], “channel”: “user_alice” // 用于同步通道过滤 }id: 我们使用自定义 ID前缀UUID便于识别文档类型。type: 固定字段便于查询时过滤。channel: 对应 Sync Gateway 中的通道我们计划每个用户一个通道。7.2 实现数据层管理器我们创建一个DatabaseManager单例来封装所有 Couchbase Lite 操作。import CouchbaseLiteSwift import Combine class DatabaseManager { static let shared DatabaseManager() private let database: Database private let serialQueue DispatchQueue(label: “com.example.notebook.db”) private var replicator: Replicator? private var listenerTokens [ListenerToken]() private init() { // 初始化数据库略见上文 // 创建笔记类型和 channel 的复合索引 try? createIndexes() } // MARK: - CRUD func saveNote(id: String? nil, title: String, content: String, tags: [String], channel: String) throws - String { var docId id ?? “note_\(UUID().uuidString)” try serialQueue.sync { let doc MutableDocument(id: docId) doc.setString(“note”, forKey: “type”) doc.setString(title, forKey: “title”) doc.setString(content, forKey: “content”) doc.setArray(tags, forKey: “tags”) doc.setString(channel, forKey: “channel”) doc.setDate(Date(), forKey: “updatedAt”) if id nil { doc.setDate(Date(), forKey: “createdAt”) } try database.saveDocument(doc) } return docId } func fetchNotes(forChannel channel: String) - [DictionaryObject] { // 使用 LiveQuery 监听变化更佳这里简化为一次性查询 let query QueryBuilder .select(SelectResult.all()) .from(DataSource.database(database)) .where( Expression.property(“type”).equalTo(Expression.string(“note”)) .and(Expression.property(“channel”).equalTo(Expression.string(channel))) ) .orderBy(Ordering.property(“updatedAt”).descending()) do { let results try query.execute() return results.allResults() } catch { print(“查询笔记失败: \(error)”) return [] } } // MARK: - 同步 func startSync(withGatewayURL url: URL, username: String, password: String) { serialQueue.async { [weak self] in guard let self self else { return } // 停止旧的同步器 self.replicator?.stop() let target URLEndpoint(url: url) var config ReplicatorConfiguration(database: self.database, target: target) config.replicatorType .pushAndPull config.continuous true config.authenticator BasicAuthenticator(username: username, password: password) // 只同步属于当前用户的笔记 config.channels [“user_\(username)”] let repl Replicator(config: config) let token repl.addChangeListener { change in // 将状态发布到主线程更新 UI DispatchQueue.main.async { NotificationCenter.default.post(name: .syncStatusChanged, object: change.status) } } self.listenerTokens.append(token) repl.start() self.replicator repl } } func stopSync() { replicator?.stop() listenerTokens.forEach { replicator?.removeChangeListener(withToken: $0) } listenerTokens.removeAll() } // MARK: - 清理 deinit { stopSync() try? database.close() } }7.3 在 SwiftUI 中集成与响应式更新在 SwiftUI 视图中我们可以使用State和ObservableObject来响应数据变化。import SwiftUI import Combine class NoteListViewModel: ObservableObject { Published var notes: [DictionaryObject] [] Published var syncStatus: String “未同步” private var cancellables SetAnyCancellable() private var query: Query? private var queryToken: ListenerToken? init() { setupQueryListener() setupSyncStatusObserver() } private func setupQueryListener() { // 创建 LiveQuery 监听笔记变化 guard let db DatabaseManager.shared.database else { return } let query QueryBuilder .select(SelectResult.all()) .from(DataSource.database(db)) .where(Expression.property(“type”).equalTo(Expression.string(“note”))) .orderBy(Ordering.property(“updatedAt”).descending()) self.query query self.queryToken query.addChangeListener { [weak self] change in DispatchQueue.main.async { self?.notes change.results?.allResults() ?? [] } } // 开始监听 query.execute() } private func setupSyncStatusObserver() { NotificationCenter.default.publisher(for: .syncStatusChanged) .receive(on: DispatchQueue.main) .sink { [weak self] notification in if let status notification.object as? Replicator.Status { self?.syncStatus “\(status.activity) - \(status.error?.localizedDescription ?? “”)” } } .store(in: cancellables) } func deleteNote(withId id: String) { DatabaseManager.shared.deleteNote(id: id) // LiveQuery 会自动触发更新notes 数组会刷新 } } struct NoteListView: View { StateObject private var viewModel NoteListViewModel() var body: some View { NavigationView { List(viewModel.notes, id: \.id) { note in VStack(alignment: .leading) { Text(note.string(forKey: “title”) ?? “无标题”) .font(.headline) Text(note.string(forKey: “content”) ?? “”) .font(.body) .lineLimit(2) } } .navigationTitle(“笔记”) .overlay( VStack { Spacer() HStack { Spacer() Text(viewModel.syncStatus) .font(.caption) .padding(8) .background(Color.gray.opacity(0.2)) .cornerRadius(5) .padding() } } ) } } }这个简单的例子展示了如何将 Couchbase Lite 的数据库操作、实时查询和同步状态与 SwiftUI 的声明式 UI 和响应式编程模型结合起来构建一个真正离线优先、体验流畅的应用。本文还有配套的精品资源点击获取