Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
Observe changes to a Core Data fetch request with Combine
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,
where Downstream.Input == CollectionDifference<Object>, Downstream.Failure == Error {
private let downstream: Downstream
private var fetchedResultsController: NSFetchedResultsController<Object>?
downstream: Downstream,
fetchRequest: NSFetchRequest<Object>,
context: NSManagedObjectContext
) {
self.downstream = downstream
= NSFetchedResultsController(
fetchRequest: fetchRequest,
managedObjectContext: context,
sectionNameKeyPath: nil,
cacheName: nil)
fetchedResultsController!.delegate = self
do {
try fetchedResultsController!.performFetch()
} catch {
downstream.receive(completion: .failure(error))
private var demand: Subscribers.Demand = .none
func request(_ demand: Subscribers.Demand) {
self.demand += demand
private var lastSentState: [Object] = []
private var currentDifferences = CollectionDifference<Object>([])!
private func updateDiff() {
= Array(fetchedResultsController?.fetchedObjects ?? []).difference(
from: lastSentState)
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>
) {
override var description: String {

This comment has been minimized.

Copy link

@michaello michaello commented Oct 1, 2020

Are you sure that line 81 is correct? We're applying difference on the same arrays: lastSentState.


This comment has been minimized.

Copy link
Owner Author

@mjm mjm commented Oct 1, 2020

@michaello Yeah, it looks super weird, but that is actually correct!

That line is just resetting currentDifferences to an empty state. I couldn't tell you why I opted to express that in this particular way, I guess I felt like the initializer for it was clunky.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.