GRDBCombine
SQLite、GRDB.swift 和 Combine 提供的一系列扩展
为最新版本:2020 年 6 月 26 日 • 版本 1.0.0-beta.4 • 发行说明 • 从 GRDBCombine 0.x 迁移到 GRDBCombine 1.0
要求:iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 6.0+ • Swift 5.2+ / Xcode 11.4+
Swift 版本 | GRDBCombine 版本 |
---|---|
Swift 5.2 | 1.0.0-beta.4,v0.8.1 |
Swift 5.1 | v0.8.1 |
联系方式:在 Github 问题 中报告错误并提问。
用法
要连接到数据库,请参阅支持 GRDBCombine 的数据库库 GRDB。
异步从数据库读取
此发布者读取单个值并交付它。
// DatabasePublishers.Read<[Player]>
let players = dbQueue.readPublisher { db in
try Player.fetchAll(db)
}
异步写入数据库
此发布者更新数据库并交付单个值。
// DatabasePublishers.Write<Void>
let write = dbQueue.writePublisher { db in
try Player(...).insert(db)
}
// DatabasePublishers.Write<Int>
let newPlayerCount = dbQueue.writePublisher { db -> Int in
try Player(...).insert(db)
return try Player.fetchCount(db)
}
监视数据库值的变化
此发布者在数据库变化时交付新鲜的值。
// A publisher with output [Player] and failure Error
let publisher = ValueObservation
.tracking { db in try Player.fetchAll(db) }
.publisher(in: dbQueue)
// A publisher with output Int? and failure Error
let publisher = ValueObservation
.tracking { db in try Int.fetchOne(db, sql: "SELECT MAX(score) FROM player") }
.publisher(in: dbQueue)
监视数据库事务
此发布者在数据库事务影响到观察到的区域时交付数据库连接。
// A publisher with output Database and failure Error
let publisher = DatabaseRegionObservation
.tracking(Player.all())
.publisher(in: dbQueue)
let cancellable = publisher.sink(
receiveCompletion: { completion in ... },
receiveValue: { (db: Database) in
print("Exclusive write access to the database after players have been impacted")
})
// A publisher with output Database and failure Error
let publisher = DatabaseRegionObservation
.tracking(SQLRequest<Int>(sql: "SELECT MAX(score) FROM player"))
.publisher(in: dbQueue)
let cancellable = publisher.sink(
receiveCompletion: { completion in ... },
receiveValue: { (db: Database) in
print("Exclusive write access to the database after maximum score has been impacted")
})
文档
安装
要使用GRDBCombine与Swift Package Manager,将依赖项添加到您的Package.swift
文件中
let package = Package(
dependencies: [
.package(url: "https://github.com/groue/GRDBCombine.git", ...)
]
)
要使用GRDBCombine与CocoaPods,在您的Podfile
中指定
pod 'GRDBCombine'
要使用GRDBCombine与SQLCipher,使用CocoaPods,并在您的Podfile
中指定
pod 'GRDBCombine/SQLCipher'
异步数据库访问
GRDBCombine提供执行异步数据库访问的发布者。
readPublisher(receiveOn:value:)
writePublisher(receiveOn:updates:)
writePublisher(receiveOn:updates:thenRead:)
DatabaseReader.readPublisher(receiveOn:value:)
此方法返回一个完成数据库值异步获取后完成发布者。
// DatabasePublishers.Read<[Player]>
let players = dbQueue.readPublisher { db in
try Player.fetchAll(db)
}
任何修改数据库的操作都会以错误完成订阅。
当您使用数据库队列或数据库快照时,读取必须等待任何由该队列或快照执行的其他并发数据库访问完成。
当您使用数据库池时,读取通常是阻塞的,除非达到并发读取的最大数量。在这种情况下,读取必须等待另一个读取完成。此最大数量可以进行配置。
此发布者可以从任何线程订阅。每次订阅都会在订阅时启动新的数据库访问。
获取的值在主线程上发布,除非您在receiveOn
参数中提供了特定的调度器。
DatabaseWriter.writePublisher(receiveOn:updates:)
此方法返回一个发布者,在数据库事务内部成功执行数据库更新后完成。
// DatabasePublishers.Write<Void>
let write = dbQueue.writePublisher { db in
try Player(...).insert(db)
}
// DatabasePublishers.Write<Int>
let newPlayerCount = dbQueue.writePublisher { db -> Int in
try Player(...).insert(db)
return try Player.fetchCount(db)
}
此发布者可以从任何线程订阅。每次订阅都会在订阅时启动新的数据库访问。
它会在主队列上完成,除非你为receiveOn
参数提供特定的调度程序。
当您使用数据库池,并且您的应用在执行一些数据库更新之后跟随一些慢速获取时,您可能会从writePublisher(receiveOn:updates:thenRead:)
的优化调度中受益。请参阅以下内容。
DatabaseWriter.writePublisher(receiveOn:updates:thenRead:)
此方法返回一个发布者,在数据库事务内部成功执行数据库更新后,并且随后的值被获取。
// DatabasePublishers.Write<Int>
let newPlayerCount = dbQueue.writePublisher(
updates: { db in try Player(...).insert(db) }
thenRead: { db, _ in try Player.fetchCount(db) })
}
它与writePublisher(receiveOn:updates:)
发布完全相同的值。
// DatabasePublishers.Write<Int>
let newPlayerCount = dbQueue.writePublisher { db -> Int in
try Player(...).insert(db)
return try Player.fetchCount(db)
}
区别在于最后的获取是在thenRead
函数中进行的。此函数接受两个参数:一个只读数据库连接和updates函数的结果。这使得您可以从函数传递信息到另一个函数(在上述示例代码中被忽略)。
当您使用数据库池时,此方法应用了调度优化:在thenRead
函数中,数据库状态与updates
函数左边的状态相同,同时不会阻塞任何并发写入。这可以减少数据库写入争用。有关更多信息,请参阅高级DatabasePool。
当您使用数据库队列时,保证结果完全相同,但没有应用任何调度优化。
此发布者可以从任何线程订阅。每次订阅都会在订阅时启动新的数据库访问。
它会在主队列上完成,除非你为receiveOn
参数提供特定的调度程序。
数据库观察
数据库观察发布者基于GRDB的ValueObservation和DatabaseRegionObservation。有关更多信息,请参阅它们的文档。如果您的应用需要GRDBCombine未内置的更改通知,请检查通用数据库更改观察章节。
ValueObservation.publisher(in:scheduling:)
GRDB的ValueObservation跟踪数据库值的改变。你可以将其转换为Combine发布者
let observation = ValueObservation.tracking { db in
try Player.fetchAll(db)
}
// A publisher with output [Player] and failure Error
let publisher = observation.publisher(in: dbQueue)
此发布者与ValueObservation具有相同的行为
-
它在最终改变之前会通知初始值。
-
它可能会将后续的更改合并为单个通知。
-
它可能会通知连续的相同值。你可以使用Combine操作符
removeDuplicates()
过滤出不需要的重复项,但我们建议你同时查看removeDuplicates() GRDB操作符。 -
数据库连接关闭后,它停止输出任何值。但它永远不会完成。
-
默认情况下,它会在主线程上异步地通知初始值、最终修改和错误。
这可以通过
scheduling
参数进行配置。它不接受Combine调度器,而是接受GRDB调度器。例如,
.immediate
调度器确保在发布者订阅时立即通知初始值。它可以帮助你的应用在没有等待任何异步通知的情况下更新用户界面// Immediate notification of the initial value let cancellable = observation .publisher( in: dbQueue, scheduling: .immediate) // <- .sink( receiveCompletion: { completion in ... }, receiveValue: { (players: [Player]) in print("Fresh players: \(players)") }) // <- here "fresh players" is already printed.
注意,
.immediate
调度器要求从主线程订阅发布者。否则,它会导致未捕获的错误。
有关更多信息,请参阅ValueObservation Scheduling。
当你与combineLatest操作符一起组合ValueObservation发布者时,你会失去 所有关于数据一致性的保证。
相反,将请求组合成一个单独的 ValueObservation,如下所示(这是在演示应用程序中使用的技术)
// DATA CONSISTENCY GUARANTEED
let hallOfFamePublisher = ValueObservation
.tracking { db -> HallOfFame in
let playerCount = try Player.fetchCount(db)
let bestPlayers = try Player.limit(10).orderedByScore().fetchAll(db)
return HallOfFame(playerCount:playerCount, bestPlayers:bestPlayers)
}
.publisher(in: dbQueue)
有关更多信息,请参阅ValueObservation。
DatabaseRegionObservation.publisher(in:)
GRDB的DatabaseRegionObservation通知所有影响所跟踪数据库区域的交易。你可以将其转换为Combine发布者
let request = Player.all()
let observation = DatabaseRegionObservation.tracking(request)
// A publisher with output Database and failure Error
let publisher = observation.publisher(in: dbQueue)
此发布者可以创建并从任何线程订阅。它以“受保护的分发队列”的形式交付数据库连接,所有数据库更新都是序列化的。只有当发生数据库错误时,它才会完成。
let request = Player.all()
let cancellable = DatabaseRegionObservation
.tracking(request)
.publisher(in: dbQueue)
.sink(
receiveCompletion: { completion in ... },
receiveValue: { (db: Database) in
print("Players have changed.")
})
try dbQueue.write { db in
try Player(name: "Arthur").insert(db)
try Player(name: "Barbara").insert(db)
}
// Prints "Players have changed."
try dbQueue.write { db in
try Player.deleteAll(db)
}
// Prints "Players have changed."
有关更多信息,请参阅DatabaseRegionObservation。