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

后端 未结 3 1017
梦谈多话
梦谈多话 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: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: NSObject, NSFetchedResultsControllerDelegate, Publisher where Entity: NSManagedObject {
        typealias Output = [Entity]
        typealias Failure = Error
    
        private let request: NSFetchRequest
        private let context: NSManagedObjectContext
        private let subject: CurrentValueSubject<[Entity], Failure>
        private var resultController: NSFetchedResultsController?
        private var subscriptions = 0
    
          init(request: NSFetchRequest, context: NSManagedObjectContext) {
            if request.sortDescriptors == nil { request.sortDescriptors = [] }
            self.request = request
            self.context = context
            subject = CurrentValueSubject([])
            super.init()
        }
    
          func receive(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
            }
            CDSubscription(fetchPublisher: self, subscriber: AnySubscriber(subscriber))
        }
    
          func controllerDidChangeContent(_ controller: NSFetchedResultsController) {
            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) {
                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
            }
        }
    
    }
    

提交回复
热议问题