-
-
Save andreyz/757ec98b5e567cddd5ff55e1fd2c1e19 to your computer and use it in GitHub Desktop.
Observe changes to a Core Data fetch request with Combine
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Combine | |
import CoreData | |
extension NSManagedObjectContext { | |
func changesPublisher<Object: NSManagedObject>(for fetchRequest: NSFetchRequest<Object>) | |
-> ManagedObjectChangesPublisher<Object> | |
{ | |
ManagedObjectChangesPublisher(fetchRequest: fetchRequest, context: self) | |
} | |
} | |
struct ManagedObjectChangesPublisher<Object: NSManagedObject>: Publisher { | |
typealias Output = CollectionDifference<Object> | |
typealias Failure = Error | |
let fetchRequest: NSFetchRequest<Object> | |
let context: NSManagedObjectContext | |
init(fetchRequest: NSFetchRequest<Object>, context: NSManagedObjectContext) { | |
self.fetchRequest = fetchRequest | |
self.context = context | |
} | |
func receive<S: Subscriber>(subscriber: S) where Failure == S.Failure, Output == S.Input { | |
let inner = Inner(downstream: subscriber, fetchRequest: fetchRequest, context: context) | |
subscriber.receive(subscription: inner) | |
} | |
private final class Inner<Downstream: Subscriber>: NSObject, Subscription, | |
NSFetchedResultsControllerDelegate | |
where Downstream.Input == CollectionDifference<Object>, Downstream.Failure == Error { | |
private let downstream: Downstream | |
private var fetchedResultsController: NSFetchedResultsController<Object>? | |
init( | |
downstream: Downstream, | |
fetchRequest: NSFetchRequest<Object>, | |
context: NSManagedObjectContext | |
) { | |
self.downstream = downstream | |
fetchedResultsController | |
= NSFetchedResultsController( | |
fetchRequest: fetchRequest, | |
managedObjectContext: context, | |
sectionNameKeyPath: nil, | |
cacheName: nil) | |
super.init() | |
fetchedResultsController!.delegate = self | |
do { | |
try fetchedResultsController!.performFetch() | |
updateDiff() | |
} catch { | |
downstream.receive(completion: .failure(error)) | |
} | |
} | |
private var demand: Subscribers.Demand = .none | |
func request(_ demand: Subscribers.Demand) { | |
self.demand += demand | |
fulfillDemand() | |
} | |
private var lastSentState: [Object] = [] | |
private var currentDifferences = CollectionDifference<Object>([])! | |
private func updateDiff() { | |
currentDifferences | |
= Array(fetchedResultsController?.fetchedObjects ?? []).difference( | |
from: lastSentState) | |
fulfillDemand() | |
} | |
private func fulfillDemand() { | |
if demand > 0 && !currentDifferences.isEmpty { | |
let newDemand = downstream.receive(currentDifferences) | |
lastSentState = Array(fetchedResultsController?.fetchedObjects ?? []) | |
currentDifferences = lastSentState.difference(from: lastSentState) | |
demand += newDemand | |
demand -= 1 | |
} | |
} | |
func cancel() { | |
fetchedResultsController?.delegate = nil | |
fetchedResultsController = nil | |
} | |
func controllerDidChangeContent( | |
_ controller: NSFetchedResultsController<NSFetchRequestResult> | |
) { | |
updateDiff() | |
} | |
override var description: String { | |
"ManagedObjectChanges(\(Object.self))" | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment