Forked from mjm/ManagedObjectChangesPublisher.swift
Created
January 23, 2022 16:37
-
-
Save seanlilmateus/e3846d9da3df45f873a977dce9fa7af0 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