如何使用 Combine 分配从 Core Data 获取请求返回的元素数量?
How to use Combine to assign the number of elements returned from a Core Data fetch request?
我希望我的应用程序定期获取新记录并将它们存储在 Core Data 中。我的 UI 上有一个标签,它应该显示特定记录的元素数量,我希望随着更多记录添加到数据库中来更新该数字。作为练习,我想用Combine来完成。
我可以在应用程序启动时显示数据库中元素的数量,但是当新数据进入数据库时该数量不会更新(我通过实施手动刷新 UI) 的按钮。
以下代码在启动时显示正确数量的元素,但在添加新记录时不更新:
let replayRecordFetchRequest: NSFetchRequest<ReplayRecord> = ReplayRecord.fetchRequest()
_ = try? persistentContainer.viewContext.fetch(replayRecordFetchRequest).publisher.count().map { String(format: Constants.Strings.playsText, [=10=]) }.assign(to: \.text, on: self.playsLabel)
这是我改编的 WWDC 2019 Session 230 演讲的代码片段,但这根本不起作用(订阅者永远不会被解雇):
let replayRecordFetchRequest: NSFetchRequest<ReplayRecord> = ReplayRecord.fetchRequest()
if let replayRecords = try? replayRecordFetchRequest.execute() {
_ = replayRecords.publisher.count().map { String(format: Constants.Strings.playsText, [=11=]) }.assign(to: \.text, on: self.playsLabel)
}
所以,我直到现在才知道这一点,但并不是所有的发布者都是无限活着的。
问题是 NSFetchRequest.publisher
不是长期存在的发布者。它只是提供了一种遍历获取请求中的元素序列的方法。结果,订阅者将在元素迭代后取消。在我的例子中,我计算了取消之前发布的元素,然后将该值分配给 UI.
相反,我应该订阅对托管对象上下文的更改并将该管道分配给我的 UI。下面是一些示例代码:
extension NotificationCenter.Publisher {
func context<T>(fetchRequest: NSFetchRequest<T>) -> Publishers.CompactMap<NotificationCenter.Publisher, [T]> {
return compactMap { notification -> [T]? in
let context = notification.object as! NSManagedObjectContext
var results: [T]?
context.performAndWait {
results = try? context.fetch(fetchRequest)
}
return results
}
}
}
let playFetchRequest: NSFetchRequest<ReplayRecord> = ReplayRecord.fetchRequest()
let replayVideoFetchRequest: NSFetchRequest<ReplayVideo> = ReplayVideo.fetchRequest()
let playsPublisher = contextDidSavePublisher.context(fetchRequest: playFetchRequest).map(\.count)
let replayVideoPublisher = contextDidSavePublisher.context(fetchRequest: replayVideoFetchRequest).map(\.count)
playsSubscription = playsPublisher.zip(replayVideoPublisher).map {
String(format: Constants.Strings.playsText, [=10=], )
}.receive(on: RunLoop.main).assign(to: \.text, on: self.playsLabel)
我希望我的应用程序定期获取新记录并将它们存储在 Core Data 中。我的 UI 上有一个标签,它应该显示特定记录的元素数量,我希望随着更多记录添加到数据库中来更新该数字。作为练习,我想用Combine来完成。
我可以在应用程序启动时显示数据库中元素的数量,但是当新数据进入数据库时该数量不会更新(我通过实施手动刷新 UI) 的按钮。
以下代码在启动时显示正确数量的元素,但在添加新记录时不更新:
let replayRecordFetchRequest: NSFetchRequest<ReplayRecord> = ReplayRecord.fetchRequest()
_ = try? persistentContainer.viewContext.fetch(replayRecordFetchRequest).publisher.count().map { String(format: Constants.Strings.playsText, [=10=]) }.assign(to: \.text, on: self.playsLabel)
这是我改编的 WWDC 2019 Session 230 演讲的代码片段,但这根本不起作用(订阅者永远不会被解雇):
let replayRecordFetchRequest: NSFetchRequest<ReplayRecord> = ReplayRecord.fetchRequest()
if let replayRecords = try? replayRecordFetchRequest.execute() {
_ = replayRecords.publisher.count().map { String(format: Constants.Strings.playsText, [=11=]) }.assign(to: \.text, on: self.playsLabel)
}
所以,我直到现在才知道这一点,但并不是所有的发布者都是无限活着的。
问题是 NSFetchRequest.publisher
不是长期存在的发布者。它只是提供了一种遍历获取请求中的元素序列的方法。结果,订阅者将在元素迭代后取消。在我的例子中,我计算了取消之前发布的元素,然后将该值分配给 UI.
相反,我应该订阅对托管对象上下文的更改并将该管道分配给我的 UI。下面是一些示例代码:
extension NotificationCenter.Publisher {
func context<T>(fetchRequest: NSFetchRequest<T>) -> Publishers.CompactMap<NotificationCenter.Publisher, [T]> {
return compactMap { notification -> [T]? in
let context = notification.object as! NSManagedObjectContext
var results: [T]?
context.performAndWait {
results = try? context.fetch(fetchRequest)
}
return results
}
}
}
let playFetchRequest: NSFetchRequest<ReplayRecord> = ReplayRecord.fetchRequest()
let replayVideoFetchRequest: NSFetchRequest<ReplayVideo> = ReplayVideo.fetchRequest()
let playsPublisher = contextDidSavePublisher.context(fetchRequest: playFetchRequest).map(\.count)
let replayVideoPublisher = contextDidSavePublisher.context(fetchRequest: replayVideoFetchRequest).map(\.count)
playsSubscription = playsPublisher.zip(replayVideoPublisher).map {
String(format: Constants.Strings.playsText, [=10=], )
}.receive(on: RunLoop.main).assign(to: \.text, on: self.playsLabel)