How to: Using Combine to react to CoreData changes in the background

后端 未结 3 993
梦谈多话
梦谈多话 2020-12-30 11:26

I want to achieve the following: Whenever someone triggers a CoreData save (ie. NSManagedObjectContextDidSave notification gets sent), I\'d like to perform some

相关标签:
3条回答
  • 2020-12-30 12:01

    If you aren’t saving the context twice then you must be adding the observer twice.

    0 讨论(0)
  • 2020-12-30 12:04

    You can pass the object you want to observe to publisher(for:):

    NotificationCenter.default
      .publisher(for: .NSManagedObjectContextDidSave, object: backgroundMoc)
      .sink(receiveValue: { notification in
        // handle changes
      })
    

    That will only listen for notifications related to a background managed object context which means you can do processing on that context's queue safely.

    0 讨论(0)
  • 2020-12-30 12:20

    You can create a Publisher which informs you when something relevant for you in Core Data has changed.

    I wrote an article on this. Combine, Publishers and Core Data.

    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
            }
        }
    
    }
    
    0 讨论(0)
提交回复
热议问题