CoreData and SwiftData provide property wrappers that allow us to react to data changes (@FetchRequest
and @Query
respectively) unfortunately, if you want to move away the data handling from the view as it's the case with MVVM, you won't be able to use these property wrappers as they can only be used from View
s.
To get around that limitation we can use a NSFetchedResultsController
and conform to NSFetchedResultsControllerDelegate
to be notified about changes and with Observation we can expose the results to drive UI changes.
@Observable
final class ObservableFetchRequest<M: NSManagedObject>:
NSObject,
NSFetchedResultsControllerDelegate
{
private(set) var fetchedObjects: [M] = []
private let fetchController: NSFetchedResultsController<M>
init(
context: NSManagedObjectContext,
predicate: NSPredicate? = nil,
sortDescriptors: [NSSortDescriptor] = []
) {
guard let request = M.fetchRequest() as? NSFetchRequest<M> else {
fatalError("Failed to create fetch request for \(M.self)")
}
if let predicate {
request.predicate = predicate
}
request.sortDescriptors = sortDescriptors
fetchController = NSFetchedResultsController<M>(
fetchRequest: request,
managedObjectContext: context,
sectionNameKeyPath: nil,
cacheName: nil
)
super.init()
fetchController.delegate = self
}
func fetch() throws {
try fetchController.performFetch()
}
func setPredicate(_ predicate: NSPredicate) throws {
fetchController.fetchRequest.predicate = predicate
try fetch()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
fetchedObjects = fetchController.fetchedObjects ?? []
}
}
Being able to handle fetch requests from the view model means that we can keep all the logic that affects the fetched data in a single place and use unit tests to validate it.