Skip to content

Instantly share code, notes, and snippets.

@agiokas
Last active August 21, 2024 14:25
Show Gist options
  • Select an option

  • Save agiokas/d6db64a9c7ed44c019e5f95f5cfeee56 to your computer and use it in GitHub Desktop.

Select an option

Save agiokas/d6db64a9c7ed44c019e5f95f5cfeee56 to your computer and use it in GitHub Desktop.
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
objc_sync_enter(self)
subscriptions += 1
start = subscriptions == 1
objc_sync_exit(self)
if start {
let controller = NSFetchedResultsController(fetchRequest: request, managedObjectContext: context,
sectionNameKeyPath: nil, cacheName: nil)
controller.delegate = self
context.perform {
do {
try controller.performFetch()
let result = controller.fetchedObjects ?? []
self.subject.send(result)
} catch {
self.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
}
}
}
@agiokas
Copy link
Author

agiokas commented Mar 11, 2020

Hi,

You need to create a NSFetchRequest like

let fetchRequest: NSFetchRequest<User> = User.fetchRequest()

keep in mind that the : NSFetchRequest<User> is required!

and then pass the fetch request to the initializer

CDPublisher(request: fetchRequest, context: container.viewContext)

@mattrighetti
Copy link

Is there a reason why the publisher is a class? Most of the publishers I've seen were simple struct that inherited from Publisher

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment