Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/swift/18.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ios 如何:使用Combine对背景中的CoreData更改作出反应_Ios_Swift_Core Data_Swift5_Combine - Fatal编程技术网

Ios 如何:使用Combine对背景中的CoreData更改作出反应

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

我希望实现以下目标:每当有人触发CoreData保存时(即发送通知时),我希望基于更改的NSManagedObject执行一些后台计算。具体示例:假设在notes应用程序中,我希望异步计算所有notes中的总字数

当前的问题在于NSManagedObject上下文显式绑定到线程,不鼓励您在此线程之外使用
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
        }
    }

}