Ios 如何:使用Combine对背景中的CoreData更改作出反应
我希望实现以下目标:每当有人触发CoreData保存时(即发送通知时),我希望基于更改的NSManagedObject执行一些后台计算。具体示例:假设在notes应用程序中,我希望异步计算所有notes中的总字数 当前的问题在于NSManagedObject上下文显式绑定到线程,不鼓励您在此线程之外使用Ios 如何:使用Combine对背景中的CoreData更改作出反应,ios,swift,core-data,swift5,combine,Ios,Swift,Core Data,Swift5,Combine,我希望实现以下目标:每当有人触发CoreData保存时(即发送通知时),我希望基于更改的NSManagedObject执行一些后台计算。具体示例:假设在notes应用程序中,我希望异步计算所有notes中的总字数 当前的问题在于NSManagedObject上下文显式绑定到线程,不鼓励您在此线程之外使用NSManagedObjects 我在我的SceneDelegate中设置了两个NSManagedObjectContexts: let context=(UIApplication.shared
NSManagedObject
s
我在我的SceneDelegate
中设置了两个NSManagedObjectContext
s:
let context=(UIApplication.shared.delegate as!AppDelegate)。persistentContainer.viewContext
让backgroundContext=(UIApplication.shared.delegate为!AppDelegate).persistentContainer.newBackgroundContext()
我还通过NotificationCenter.default.publisher(用于:.NSManagedObjectContextDidSave)
订阅了通知,并且在我仅触发一个managedObjectContext.save()后,我收到了两次保存通知。但是,这两个通知都是从同一个线程(即UIThread)发送的,并且用户字典中的所有NSManagedObjects
都有。managedObjectContext
是viewContext
而不是backgroundContext
我的想法是根据关联的NSManagedObjectContext
是否为后台上下文来过滤通知,因为我假设通知也在(私有)DispatchQueue上发送,但似乎所有通知都在UIThread上发送,并且从不使用后台上下文
有没有办法解决这个问题?这是虫子吗?如何根据backgroundContext
检索通知,并在关联的DispatchQueue上运行下游任务?您可以将要观察的对象传递给发布者(for:)
:
它将只侦听与后台托管对象上下文相关的通知,这意味着您可以安全地在该上下文的队列上进行处理。您可以创建一个发布服务器,当核心数据中与您相关的内容发生更改时通知您
我写了一篇关于这个的文章
导入联合收割机
导入CoreData
进口基金会
类CDPublisher:NSObject,NSFetchedResultsControllerDelegate,发布者,其中实体:NSManagedObject{
typealias输出=[实体]
typealias故障=错误
私有let请求:NSFetchRequest
私有let上下文:NSManagedObjectContext
私人租赁主体:CurrentValueSubject
私有var结果控制器:NSFetchedResultsController?
私有变量订阅=0
init(请求:NSFetchRequest,上下文:NSManagedObjectContext){
如果request.sortDescriptors==nil{request.sortDescriptors=[]}
self.request=请求
self.context=context
主题=CurrentValueSubject([])
super.init()
}
func接收(订阅方:S)
其中S:Subscriber,CDPublisher.Failure==S.Failure,CDPublisher.Output==S.Input{
var start=false
同步(自){
订阅数+=1
开始=订阅==1
}
如果开始{
让controller=NSFetchedResultsController(fetchRequest:request,managedObjectContext:context,
sectionNameKeyPath:nil,cacheName:nil)
controller.delegate=self
做{
尝试controller.performFetch()
让结果=controller.fetchedObjects???[]
主题。发送(结果)
}抓住{
subject.send(完成:。失败(错误))
}
结果控制器=控制器为?NSFetchedResultsController
}
CDSubscription(fetchPublisher:self,subscriber:AnySubscriber(subscriber))
}
func controllerDidChangeContent(\控制器:NSFetchedResultsController){
让结果=controller.fetchedObjects为?[Entity]??[]
主题。发送(结果)
}
私有函数dropSubscription(){
objc_同步_输入(自)
订阅-=1
let stop=subscriptions==0
objc_同步_退出(自)
如果停止{
resultController?.delegate=nil
resultController=nil
}
}
私有类订阅:订阅{
私有发行商:CDPublisher?
私有var可取消:任何可取消?
@可丢弃结果
init(fetchPublisher:CDPublisher,订阅者:AnySubscriber){
self.fetchPublisher=fetchPublisher
subscriber.receive(订阅:self)
Cancelable=fetchPublisher.subject.sink(receiveCompletion:{completion in
订阅服务器接收(完成:完成)
},receiveValue:{中的值
_=订阅服务器接收(值)
})
}
func请求(demand:Subscribers.demand){}
func cancel(){
可取消?.cancel()
可取消=零
fetchPublisher?.dropSubscription()
fetchPublisher=nil
}
}
}
如果不保存两次上下文,则必须添加两次观察者。publisher(for:)还有一个名为object的参数,默认为nil。尝试将对象参数设置为要观察的上下文。保留一个背景上下文不是一个好主意,否则它将充满不必要的对象。我将接受这个答案,因为它确实会过滤正确的上下文,并且后续操作将在正确的线程上执行。但是我想在这里留下一些信息,在我的场景中,您不会得到一个NSManagedObjectContextDidSave
的后台通知
NotificationCenter.default
.publisher(for: .NSManagedObjectContextDidSave, object: backgroundMoc)
.sink(receiveValue: { notification in
// handle changes
})
import Combine
import CoreData
import Foundation
class CDPublisher<Entity>: NSObject, NSFetchedResultsControllerDelegate, Publisher where Entity: NSManagedObject {
typealias Output = [Entity]
typealias Failure = Error
private let request: NSFetchRequest<Entity>
private let context: NSManagedObjectContext
private let subject: CurrentValueSubject<[Entity], Failure>
private var resultController: NSFetchedResultsController<NSManagedObject>?
private var subscriptions = 0
init(request: NSFetchRequest<Entity>, context: NSManagedObjectContext) {
if request.sortDescriptors == nil { request.sortDescriptors = [] }
self.request = request
self.context = context
subject = CurrentValueSubject([])
super.init()
}
func receive<S>(subscriber: S)
where S: Subscriber, CDPublisher.Failure == S.Failure, CDPublisher.Output == S.Input {
var start = false
synchronized(self) {
subscriptions += 1
start = subscriptions == 1
}
if start {
let controller = NSFetchedResultsController(fetchRequest: request, managedObjectContext: context,
sectionNameKeyPath: nil, cacheName: nil)
controller.delegate = self
do {
try controller.performFetch()
let result = controller.fetchedObjects ?? []
subject.send(result)
} catch {
subject.send(completion: .failure(error))
}
resultController = controller as? NSFetchedResultsController<NSManagedObject>
}
CDSubscription(fetchPublisher: self, subscriber: AnySubscriber(subscriber))
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
let result = controller.fetchedObjects as? [Entity] ?? []
subject.send(result)
}
private func dropSubscription() {
objc_sync_enter(self)
subscriptions -= 1
let stop = subscriptions == 0
objc_sync_exit(self)
if stop {
resultController?.delegate = nil
resultController = nil
}
}
private class CDSubscription: Subscription {
private var fetchPublisher: CDPublisher?
private var cancellable: AnyCancellable?
@discardableResult
init(fetchPublisher: CDPublisher, subscriber: AnySubscriber<Output, Failure>) {
self.fetchPublisher = fetchPublisher
subscriber.receive(subscription: self)
cancellable = fetchPublisher.subject.sink(receiveCompletion: { completion in
subscriber.receive(completion: completion)
}, receiveValue: { value in
_ = subscriber.receive(value)
})
}
func request(_ demand: Subscribers.Demand) {}
func cancel() {
cancellable?.cancel()
cancellable = nil
fetchPublisher?.dropSubscription()
fetchPublisher = nil
}
}
}