GRDB.swift 数据库观察完全指南:ValueObservation、DatabaseRegionObservation 与 TransactionObserver 实战解析
2026/9/16 10:32:53 网站建设 项目流程

GRDB.swift 数据库观察完全指南:ValueObservation、DatabaseRegionObservation 与 TransactionObserver 实战解析

【免费下载链接】GRDB.swiftA toolkit for SQLite databases, with a focus on application development项目地址: https://gitcode.com/GitHub_Trending/gr/GRDB.swift

数据库变化是驱动现代应用界面刷新的核心信号。本指南以 GRDB.swift 官方文档 DatabaseObservation.md 为主线,系统讲解 GRDB 面向应用开发的四大观察机制——ValueObservation(值观察)、DatabaseRegionObservation(区域观察)、afterNextTransaction(事务回调)以及底层TransactionObserver(事务观察者协议),并深入对应源码与测试,帮助读者掌握从"感知数据库变化"到"以最小成本刷新 UI"的完整实战方案。

观察体系总览:SQLite 通知能力在 GRDB 中的四种用法

GRDB 的观察能力建立在 SQLite 的一项原生特性之上:SQLite 会向其宿主应用通知数据库中执行的变化,以及事务的提交(commit)与回滚(rollback)。GRDB 将这一特性包装为四个由高到低、层层递进的使用层次(见 DatabaseObservation.md 的 Overview):

观察方式观察粒度典型场景
ValueObservation数据库监听玩家列表、统计数字等"值"的变化,拿到新鲜值直接渲染
DatabaseRegionObservation数据库区域事务提交后立刻得到通知,且保证在其他线程写入之前回调
Database/afterNextTransaction(onCommit:onRollback:)单次事务只在"某条数据成功落盘"后执行一次副作用(如启动定位监控)
TransactionObserver底层事务与逐条变更需要逐行、逐列、甚至在提交前介入的进阶需求

其中前三种高层次的 API 全部由第四种——TransactionObserver协议——在底层支撑实现,理解底层协议有助于你驾驭所有观察功能。

ValueObservation:观察数据库值的实时变化

ValueObservation是 GRDB 面向应用开发最常用的观察类型,它跟踪数据库请求结果的变化,并在数据库发生变化时通知新鲜值(见 Extension/ValueObservation.md)。

三步上手

  1. 保证唯一数据库连接常驻:观察期间必须保持一个DatabaseQueueDatabasePool处于打开状态,观察会持有该连接直到停止(见ValueObservation.swift源码中start(in:)的实现,连接由观察生命周期托底)。

  2. 用闭包定义被观察的值

// 跟踪所有玩家 let observation = ValueObservation.tracking { db in try Player.fetchAll(db) } // 等价简写 let observation = ValueObservation.tracking(Player.fetchAll)

被观察的值没有类型限制:可以执行多次请求、跨多张表、甚至使用原生 SQL。源码中的ValueObservationTrackingMode枚举(见 ValueObservation.swift)区分了三种跟踪模式:显式常量区域(constantRegion)、从取回结果推导的常量区域(constantRegionRecordedFromSelection)、以及每次 fetch 都会变化的非恒定区域(nonConstantRegionRecordedFromSelection),后者典型如Player.fetchOne(db, id: Int.random(in: 1...1000))

  1. 启动观察并处理结果
let cancellable = observation.start(in: dbQueue) { error in // 处理错误 } onChange: { (players: [Player]) in print("Fresh players", players) }
  1. 停止观察:调用start返回的DatabaseCancellablecancel();当该对象被释放时观察也会自动停止。
cancellable.cancel()

消费方式的三个出口

ValueObservation可以被转换成AsyncSequence、Combine Publisher 或 RxSwift Observable(Rx 需借助社区库 RxGRDB):

  • Async 序列(默认在协作线程池上调度,源码见values(in:scheduling:bufferingPolicy:),ValueObservation.swift):
do { for try await players in observation.values(in: dbQueue) { print("Fresh players", players) } } catch { // 处理错误 }
  • Combine Publisher(默认异步调度在主队列,见publisher(in:scheduling:),ValueObservation.swift):
let cancellable = observation.publisher(in: dbQueue).sink { completion in // 处理完成 } receiveValue: { (players: [Player]) in print("Fresh players", players) }

行为契约:必须知道的六条规则

ValueObservation的行为遵循以下固定契约:

  1. 先通知初始值:观察启动时立即产生一次初始值,之后才是后续变化。
  2. 只通知已提交到磁盘的变化:回滚的事务不会产生任何通知。
  3. 默认每次变更都通知:只要被取回值的任意组成部分(列、行等)被修改就触发;可通过"指定跟踪区域"(见下文)收紧。
  4. 默认在主 Actor 上异步通知:初始值与后续错误、变化都在主 Actor 上异步投递;可通过调度参数改变。
  5. 允许合并通知:短时间内多次连续变更可能被合并为一次通知。
  6. 可能连续出现相同值:可用removeDuplicates()过滤重复。

重要:ValueObservation并不适合所有场景。如果应用必须处理每一次变化、不允许合并,或者需要在数据库文件被进一步修改之前处理变化,应改用DatabaseRegionObservation;如果需要在提交到磁盘之前介入,则应使用TransactionObserver(原文出处见 Extension/ValueObservation.md)。

调度(Scheduling):控制回调发生在哪条线程

默认情况下,错误与变化回调都是@MainActor隔离的(源码中@MainActor public func start(...)的默认参数为.mainActor,见 ValueObservation.swift):

let cancellable = observation.start(in: dbQueue) { error in // 此闭包运行在主 Actor } onChange: { value in // 此闭包运行在主 Actor print("Fresh value", value) }

通过向start()传入scheduling参数可定制调度策略:

  • .immediate:全部值在主 Actor 上通知,且第一个值在观察启动时立即同步送达。这对图形应用极其有用——无需等待异步首值、无需实现空态或加载屏即可立即配置视图;但代价是首次 fetch 期间 UI 无响应,只建议用于非常快的数据库请求
let cancellable = observation .start(in: dbQueue, scheduling: .immediate) { error in // 主 Actor 上调用 } onChange: { value in // 主 Actor 上调用 print("Fresh value", value) } // <- 执行到这里时 "Fresh value" 已经打印过了
  • .async(onQueue:):在指定的 DispatchQueue 上异步调度。必须提供串行队列——并发队列(如DispatchQueue.global(qos: .default))会打乱新鲜值的通知顺序:
let myQueue: DispatchQueue let cancellable = observation .start(in: dbQueue, scheduling: .async(myQueue)) { error in // 在 myQueue 上异步调用 } onChange: { value in // 在 myQueue 上异步调用 print("Fresh value", value) }
  • .task:在 Swift 协作线程池上异步调度,也是把观察转为 async 序列时的隐式调度器:
let sharedObservation = observation.shared(in: dbQueue, scheduling: .task) do { for try await players in sharedObservation.values() { print("Fresh players", players) } }

此外scheduling还影响数据库 fetch 本身的执行线程

  • .immediate下初始 fetch 总在观察启动时同步执行于主 Actor;
  • 默认.async下初始 fetch 总是异步执行,绝不阻塞主线程;
  • 默认情况下,新鲜值在数据库被修改后立即fetch——主线程改库会在主线程触发 fetch。若要保证"新鲜值绝不在主线程 fetch",需要使用DatabasePool配合tracking(regions:fetch:)trackingConstantRegion(_:)创建的优化观察(使用前务必通读这两个方法的文档,否则可能漏掉某些数据库变化)。

共享观察:一份 fetch,多点通知

共享ValueObservation可以节省数据库资源:当一次数据库变化发生时,新鲜值只 fetch 一次,然后广播给所有客户端(见shared(in:scheduling:extent:)):

// SharedValueObservation<[Player]> let sharedObservation = ValueObservation .tracking { db in try Player.fetchAll(db) } .shared(in: dbQueue)

ValueObservationSharedValueObservation几乎等价,但后者没有map这类操作符;需要转换时可借助 Combine:

let cancellable = try sharedObservation .publisher() // 把共享观察转为 Combine Publisher .map { ... } // 使用 Combine 的 map .sink(...)

指定跟踪区域:观察与取回解耦

标准的tracking(_:)是"跟踪取回的值",而tracking(region:_:fetch:)让你把被观察的区域被取回的值完全分离。例如,只在玩家score列变化时去取回 id=1 玩家的整行:

let observation = ValueObservation.tracking( // 定义被跟踪的数据库区域(player 表 id=1 的 score 列) region: Player.select(\.score).filter(id: 1), // 定义该区域变化后要取回什么(id=1 的玩家) fetch: { db in try Player.fetchOne(db, id: 1) } )

任何遵循DatabaseRegionConvertible的类型——FetchRequestDatabaseRegionTable等——都可作为跟踪区域。DatabaseRegion的底层实现见 DatabaseRegion.swift:它内部以[CaseInsensitiveIdentifier: TableRegion]描述"哪些表 × 哪些列 × 哪些行号",且表名不区分大小写;nil字典则代表覆盖整个数据库的fullDatabase区域。

无法被自动检测的变化:notifyChanges(in:)

以下场景ValueObservation不会自动 fetch 并通知新鲜值:

  • 外部数据库连接执行的变化;
  • 非 GRDB 编译执行的DELETE/INSERT/UPDATE之外的 SQLite 语句造成的变化;
  • 数据库 schema 变更、sqlite_master等内部系统表的变更;
  • WITHOUT ROWID表上的变更。

遇到上述情况,可在写事务中显式调用Database/notifyChanges(in:)手动通知:

try dbQueue.write { db in // 通知观察:数据库发生了某些变化 try db.notifyChanges(in: .fullDatabase) // 通知观察:player 表发生了变化 try db.notifyChanges(in: Player.all()) // 等价写法 try db.notifyChanges(in: Table("player")) }

性能要点:减少数据库争用

ValueObservation的触发依据是"可能影响被跟踪值的事务",精确地说它跟踪的是DatabaseRegion而非值本身。例如跟踪玩家的最高分时,任何触及player.score列的事务(无论插入、更新还是删除)都会触发观察,即便最高分并未改变。这是性能考量的关键认知(原文出处见 Extension/ValueObservation.md):

  • 活跃的观察会占用受限的数据库资源,触发时 fetch 新鲜值,可能延迟其他组件的读写访问,造成数据库争用。GRDB 给出的优化建议是:
    • 控制观察数量:不要对列表中的每个元素单独建观察,而应把整个列表放进一个观察里;
    • 及时停止观察:例如UIViewControllerviewWillAppear启动观察、viewWillDisappear停止;SwiftUI 应用可借助 GRDBQuery 的View.queryObservation(_:)
    • 尽量共享观察ValueObservation.start的每次调用都会触发独立的值刷新,多个组件关注同一值时用shared(in:scheduling:extent:)
    • map(_:)做后处理:把"取原始值 + 计算"拆成trackingmapmap的执行不阻塞数据库访问也不阻塞主线程:
// 普通写法(fetch 与计算耦合在事务内) let observation = ValueObservation.tracking { db -> MyValue in let players = try Player.fetchAll(db) return computeMyValue(players) } // 优化写法(计算移出 fetch) let observation = ValueObservation .tracking { db try Player.fetchAll(db) } .map { players in computeMyValue(players) }
  • 截断型 WAL checkpoint 的副作用:在DatabasePool上、且数据库缺失 wal 文件时启动观察,即使内容未变化也会在启动时通知两次值——因为无法创建检测启动期间变化所需的 wal 快照。若应用执行截断型 checkpoint(Database/checkpoint(_:on:)PRAGMA wal_checkpoint),可在启动观察前用一个空事务(如创建再丢弃临时表)重建非空的 wal 文件来规避。

DatabaseRegionObservation:事务级区域观察

当应用需要在处理完每一次影响指定区域的事务后立刻得到通知,且保证"通知发生在任何其他线程有机会进一步写入之前"时,应使用DatabaseRegionObservation(见 Extension/DatabaseRegionObservation.md)。

文档特别强调:这种"提交后、其他人写入前"的强保证大多数应用并不真正需要,它们更想要的是新鲜值——所以先评估ValueObservation是否满足需求,再决定是否使用本 API。

用法

用一条或多条请求创建观察,然后从DatabaseQueue/DatabasePool启动:

// 跟踪完整的 player 表 let observation = DatabaseRegionObservation(tracking: Player.all()) let cancellable = try observation.start(in: dbQueue) { error in // 处理错误 } onChange: { (db: Database) in print("Players were changed") }

写库即触发通知:

try dbQueue.write { db in try Player(name: "Arthur").insert(db) } // 打印 "Players were changed"

同样可用cancellable.cancel()或释放对象来停止;同样可转成 Combine Publisher(Rx 借助 RxGRDB)。DatabaseRegionObservation接受任何DatabaseRegionConvertible类型作为跟踪目标:

// 观察 player 表的 score 列(QueryInterface 写法) let observation = DatabaseRegionObservation( tracking: Player.select(\.score)) // 观察 player 表的 score 列(原生 SQL 写法) let observation = DatabaseRegionObservation( tracking: SQLRequest("SELECT score FROM player")) // 同时观察 player 与 team 两张表 let observation = DatabaseRegionObservation( tracking: Table("player"), Table("team")) // 观察整个数据库 let observation = DatabaseRegionObservation( tracking: .fullDatabase)

DatabaseRegionObservation同样存在"无法检测的变化"清单(外部连接、非 GRDB 语句、schema 变更、WITHOUT ROWID表),处理方式与ValueObservation一致——在写事务中调用db.notifyChanges(in:)显式通知。相关测试可参考 Tests/GRDBTests/Core/DatabaseRegionObservationTests.swift 与 Combine 版 Tests/GRDBTests/GRDBCombineTests/DatabaseRegionObservationPublisherTests.swift。

afterNextTransaction:只关心"这一次事务"是否成功

Database/afterNextTransaction(onCommit:onRollback:)用于在下一个或当前事务完成时执行一次回调,非常适合把数据库与其他资源(文件、系统服务)同步。

典型场景:落盘成功才启动副作用

源码文档给出的经典示例(见 TransactionObserver.swift)是CLLocationManager的监控启动——只有当CLRegion成功写入数据库并提交后才开始监控:

/// 向数据库插入 region,并在成功插入后开始监控。 func startMonitoring(_ db: Database, region: CLRegion) throws { // 确保数据库位于事务(savepoint)内 try db.inSavepoint { // 把 region 存入数据库 try insert(...) // 当且仅当插入最终被提交时开始监控 db.afterNextTransaction { _ in // locationManager 偏好主队列: DispatchQueue.main.async { locationManager.startMonitoring(for: region) } } return .commit } }

如果事务最终回滚(显式回滚或因错误回滚),onCommit不会被执行,从而避免"数据库没存上却启动了监控"的不一致状态。

实现与约束

从源码看,afterNextTransaction本质上是把一个内部TransactionHandler观察者以.nextTransaction的 extent 注册到连接上(见 TransactionObserver.swift),TransactionHandler忽略所有变更事件,只在databaseDidCommit/databaseDidRollback中触发回调。使用注意:

  • onCommitonRollback写入分派队列上串行执行,与其他数据库更新互斥;
  • 前置条件:数据库连接不能是只读的(源码中GRDBPrecondition(!isReadOnly, ...)会直接触发断言,因为只读事务不会被通知给观察者);
  • onRollback有默认空实现,可按需只提供onCommit

TransactionObserver:支撑一切的底层协议

TransactionObserver是所有观察功能的底层支撑(见 Extension/TransactionObserver.md)。它在事务提交到磁盘之前就通知逐条变更(插入、更新、删除),并在提交、回滚时给出对应回调。

注册观察者

观察者通过DatabaseWriter/add(transactionObserver:extent:)(作用于队列/池)或Database/add(transactionObserver:extent:)(作用于连接)注册:

let observer = MyObserver() dbQueue.add(transactionObserver: observer)

默认情况下数据库对观察者持有弱引用:观察者不被保留,一旦被释放即自动停止接收通知。

回调时序:提交与回滚的完整生命周期

变更通过databaseDidChange(with:)通知(包括外键ON DELETE/ON UPDATE与 SQL 触发器引发的间接变更);事务完成通过databaseWillCommit()databaseDidCommit(_:)databaseDidRollback(_:)通知。被通知的变更在事务提交(databaseDidCommit)之前并未真正写入磁盘,databaseDidRollback则确认其失效:

try dbQueue.write { db in try db.execute(sql: "INSERT ...") // 1. didChange try db.execute(sql: "UPDATE ...") // 2. didChange } // 3. willCommit, 4. didCommit try dbQueue.inTransaction { db in try db.execute(sql: "INSERT ...") // 1. didChange try db.execute(sql: "UPDATE ...") // 2. didChange return .rollback // 3. didRollback } try dbQueue.write { db in try db.execute(sql: "INSERT ...") // 1. didChange throw SomeError() } // 2. didRollback

显式事务之外的语句也不会漏掉:

try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "INSERT ...") // 1. didChange, 2. willCommit, 3. didCommit try db.execute(sql: "UPDATE ...") // 4. didChange, 5. willCommit, 6. didCommit }

Savepoint 场景:被挂起(savepoint 内部)的变更只有在其 release 之后才被通知,确保通知的事件都是"有机会被提交"的事件:

try dbQueue.inTransaction { db in try db.execute(sql: "INSERT ...") // 1. didChange try db.execute(sql: "SAVEPOINT foo") try db.execute(sql: "UPDATE ...") // 延迟 try db.execute(sql: "UPDATE ...") // 延迟 try db.execute(sql: "RELEASE SAVEPOINT foo") // 2. didChange, 3. didChange try db.execute(sql: "SAVEPOINT bar") try db.execute(sql: "UPDATE ...") // 不通知 try db.execute(sql: "ROLLBACK TO SAVEPOINT bar") try db.execute(sql: "RELEASE SAVEPOINT bar") return .commit // 4. willCommit, 5. didCommit }

databaseWillCommit抛错会把错误暴露给应用代码:

do { try dbQueue.inTransaction { db in ... return .commit // 1. willCommit(抛错), 2. didRollback } } catch { // 3. 事务观察者抛出的错误 }

两条硬性约束:

  • 所有回调都在写入分派队列中调用,与所有数据库更新串行化;
  • databaseDidChangedatabaseWillCommit回调不得以任何方式访问被观察的写入连接;该限制不适用于databaseDidCommit/databaseDidRollback(它们可以使用传入的Database参数)。

事件过滤:observes(eventsOfKind:)

默认的事件过滤机制是observes(eventsOfKind:)——它在数据库查询执行前只调用一次,即可完全禁用变更跟踪,是最高效、被推荐的过滤方式:

// 调用 observes(eventsOfKind:) 一次。 // 之后要么为每一行更新调用 databaseDidChange(with:),要么完全不调用。 try db.execute(sql: "UPDATE player SET score = score + 1")

DatabaseEventKind参数能区分插入/删除/更新,并告知将要被修改的列。例如只关注player表:

class PlayerObserver: TransactionObserver { func observes(eventsOfKind eventKind: DatabaseEventKind) -> Bool { // 只观察 "player" 表上的变化 eventKind.tableName == "player" } func databaseDidChange(with event: DatabaseEvent) { // 此方法只会在 "player" 表变化时被调用 } }

即使对所有事件类型都返回false,观察者仍然会收到事务通知。该过滤机制也意味着观察者感知不到非 GRDB 编译执行的语句造成的变化——可通过实现databaseEventObservationStrategy解除此限制。

观察范围(Extent):观察者能活多久

可通过remove(transactionObserver:)随时显式停止,也可用add(transactionObserver:extent:)的 extent 参数指定生命周期(三种取值定义见 TransactionObserver.swift):

let observer = MyObserver() // 数据库队列或池上: dbQueue.add(transactionObserver: observer) // 默认 extent dbQueue.add(transactionObserver: observer, extent: .observerLifetime) dbQueue.add(transactionObserver: observer, extent: .nextTransaction) dbQueue.add(transactionObserver: observer, extent: .databaseLifetime) // 数据库连接上: dbQueue.inDatabase { db in db.add(transactionObserver: ...) }
  • .observerLifetime(默认):数据库持有弱引用,观察者被释放即自动结束,期间通知所有变化与事务;
  • .nextTransaction:激活到当前或下一个事务完成,数据库对其保持强引用,直到databaseDidCommitdatabaseDidRollback被调用后不再通知;
  • .databaseLifetime:数据库保留并通知观察者,直到连接关闭。

此外,观察者还可以在事务中途"退场":调用stopObservingDatabaseChangesUntilNextTransaction()后,databaseDidChange在当前事务结束前不再被调用——典型用于"只关心事务内是否出现过某类变化":

class PlayerObserver: TransactionObserver { var playerTableWasModified = false func observes(eventsOfKind eventKind: DatabaseEventKind) -> Bool { eventKind.tableName == "player" } func databaseDidChange(with event: DatabaseEvent) { playerTableWasModified = true // 没必要继续跟踪后续变化: stopObservingDatabaseChangesUntilNextTransaction() } }

进阶:SQLite Pre-Update Hooks

当 SQLite 以SQLITE_ENABLE_PREUPDATE_HOOK选项编译时,TransactionObserver会获得一个额外回调databaseWillChange(with:),可在变更发生前观察被修改行的各列初始值/最终值:

protocol TransactionObserver: AnyObject { #if SQLITE_ENABLE_PREUPDATE_HOOK /// 在数据库变更(插入、更新或删除)之前,附带变更信息 /// (该行各列的初始值 / 最终值)进行通知。 /// /// 事件仅在本次方法调用期间有效;如需保存请复制:event.copy()。 func databaseWillChange(with event: DatabasePreUpdateEvent) #endif }

启用该能力的两种途径(详见 Extension/TransactionObserver.md):

  1. CocoaPods + 自定义编译选项:在 Podfile 的post_install中为 GRDB.swift target 追加 Swift 与 C 编译定义:
pod 'GRDB.swift' post_install do |installer| installer.pods_project.targets.select { |target| target.name == "GRDB.swift" }.each do |target| target.build_configurations.each do |config| # 启用额外的 GRDB API config.build_settings['OTHER_SWIFT_FLAGS'] = "$(inherited) -D SQLITE_ENABLE_PREUPDATE_HOOK" # 启用额外的 SQLite API config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = "$(inherited) GRDB_SQLITE_ENABLE_PREUPDATE_HOOK=1" end end end

警告:务必使用匹配的平台版本!在较低版本设备上会触发运行时错误。 注意:GCC_PREPROCESSOR_DEFINITIONS中的GRDB_SQLITE_ENABLE_PREUPDATE_HOOK=1用于补齐系统<sqlite3.h>缺失的 C 函数原型;一旦 Xcode SDK 自带完整头文件而出现重复定义编译错误,只需移除该项。

  1. 自定义 SQLite 构建:参考 CustomSQLiteBuilds.md,在编译 SQLite 时激活SQLITE_ENABLE_PREUPDATE_HOOK选项。

底层观察者的"盲区"清单

TransactionObserver无法自动通知的变化与事务包括:

  • 只读事务;
  • 外部数据库连接执行的变化与事务;
  • 非 GRDB 编译执行的DELETE/INSERT/UPDATE之外的语句(可通过实现databaseEventObservationStrategy解除);
  • 数据库 schema 变更、sqlite_master等内部系统表变更;
  • WITHOUT ROWID表上的变更;
  • ON CONFLICT REPLACE触发的重复行删除(此例外未来 SQLite 版本可能改变)。

与高层 API 相同的补救手段是显式调用Database/notifyChanges(in:),此时databaseDidChange()回调会被触发:

try dbQueue.write { db in // 通知观察者:数据库发生了某些变化 try db.notifyChanges(in: .fullDatabase) // 通知观察者:player 表发生了变化 try db.notifyChanges(in: Player.all()) // 等价写法 try db.notifyChanges(in: Table("player")) }

若要通知 schema 变化,可通知sqlite_master表:

try dbQueue.write { db in // 通知所有观察者:sqlite_master 表变化 try db.notifyChanges(in: Table("sqlite_master")) }

选型总结:四种观察方式怎么选

面对观察需求,可按下述路径决策(对应 DatabaseObservation.md 的 Topics 分类):

  1. 要新鲜值、要刷新 UIValueObservation(或共享版SharedValueObservation、异步版AsyncValueObservation),配合Database/registerAccess(to:)进行访问注册;
  2. 要在"每次影响指定区域的事务提交后、其他写入前"被回调,且能容忍自己手动取数 →DatabaseRegionObservation
  3. 只想在"某次事务落盘"后执行一次副作用Database/afterNextTransaction(onCommit:onRollback:)
  4. 需要逐条变更、提交前介入、精细过滤或列级 pre-update 信息→ 直接实现TransactionObserver协议,并通过Database/add(transactionObserver:extent:)/DatabaseWriter/add(transactionObserver:extent:)注册,用Database/remove(transactionObserver:)/DatabaseWriter/remove(transactionObserver:)注销,按需选择Database/TransactionObservationExtent控制生命周期。

所有区域相关的类型(DatabaseRegionDatabaseRegionConvertible)都可直接用于DatabaseRegionObservationValueObservation的区域跟踪。从 ValueObservation.swift 的源码结构可以看到,ValueObservation通过可配置的ValueReducertrackingMode组合出"值变化感知 + 区域跟踪"的完整能力,而这一切最终都落在TransactionObserver提供的提交/回滚生命周期之上。掌握这四层机制,你便拥有了从"数据库变化"到"界面刷新"之间的完整、可控的响应链路。

【免费下载链接】GRDB.swiftA toolkit for SQLite databases, with a focus on application development项目地址: https://gitcode.com/GitHub_Trending/gr/GRDB.swift

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询