Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions Sources/_CoreDataDependency/CoreDataCombineObjectResolver.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//
// CoreDataCombineObjectResolver.swift
// swift-dependencies-additions
//
// Created by Robert Nash on 12/09/2025.
//

import CoreData
import Combine

/// Resolves Core Data objects from their `NSManagedObjectID`s and publishes updates.
public final class CoreDataCombineObjectResolver<T: NSManagedObject> {

private let idsSubject = CurrentValueSubject<[NSManagedObjectID], Never>([])
public let publisher: AnyPublisher<[T], Never>

private let context: NSManagedObjectContext
private var cancellables = Set<AnyCancellable>()

public init(
context: NSManagedObjectContext,
deliverOn: DispatchQueue = .main,
debounce: DispatchQueue.SchedulerTimeType.Stride? = nil,
sortDescriptors: [NSSortDescriptor]? = nil
) {
self.context = context

var publisher: AnyPublisher<[NSManagedObjectID], Never> = idsSubject
.removeDuplicates(by: { lhs, rhs in
lhs.count == rhs.count && lhs.elementsEqual(rhs, by: { $0 == $1 })
})
.eraseToAnyPublisher()
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We erase to AnyPublisher early so we can reassign the variable after applying operators like debounce, because each Combine operator produces a different concrete publisher type.


if let d = debounce {
publisher = publisher
.debounce(for: d, scheduler: deliverOn)
.eraseToAnyPublisher()
}

self.publisher = publisher
.flatMap { ids -> AnyPublisher<[T], Never> in
Deferred {
Future { promise in
context.perform {
do {
guard let entityName = T.entity().name else {
promise(.success([])); return
}
let request = NSFetchRequest<T>(entityName: entityName)
request.predicate = NSPredicate(format: "SELF IN %@", Array(ids))
request.sortDescriptors = sortDescriptors

let results = try context.fetch(request)
promise(.success(results))
} catch {
promise(.success([]))
}
}
}
}
.eraseToAnyPublisher()
}
.receive(on: deliverOn)
.eraseToAnyPublisher()
}

public func resolve(_ ids: [NSManagedObjectID]) {
idsSubject.send(ids)
}
}