Implemented the Fetcher class.

This commit is contained in:
Javier Cicchelli 2023-04-17 00:11:05 +02:00
parent 825354ef6d
commit 879b6d6bbd

View File

@ -0,0 +1,154 @@
//
// Fetcher.swift
// Persistence
//
// Created by Javier Cicchelli on 16/04/2023.
// Copyright © 2023 Röck+Cöde. All rights reserved.
//
import Combine
import CoreData
/// This class fetches objects from a given managed object context and it notifies of changes in the object fetched if any.
public class Fetcher<Model: NSManagedObject>: NSObject, NSFetchedResultsControllerDelegate {
// MARK: Properties
/// The publisher that emits the changes detected to the Location entities in a given object context.
public let didChangePublisher = PassthroughSubject<[Change], Never>()
private let fetchedResultsController: NSFetchedResultsController<Model>
/// The number of sections in the data.
public var numberOfSections: Int {
fetchedResultsController.sections?.count ?? 0
}
private var inProgressChanges: [Change] = []
// MARK: Initialisers
/// Initialises the fetcher give the given parameters.
/// - Parameters:
/// - fetchRequest: The fetch request to use to get the objects.
/// - managedObjectContext: The managed object context against the fetch request is executed.
/// - sectionNameKeyPath: A key path on result objects that returns the section name.
/// - cacheName: The name of the cache file the receiver should use.
public init(
fetchRequest: NSFetchRequest<Model>,
managedObjectContext: NSManagedObjectContext,
sectionNameKeyPath: String? = nil,
cacheName: String? = nil
) {
self.fetchedResultsController = .init(
fetchRequest: fetchRequest,
managedObjectContext: managedObjectContext,
sectionNameKeyPath: sectionNameKeyPath,
cacheName: cacheName
)
super.init()
self.fetchedResultsController.delegate = self
}
// MARK: Functions
/// Perform the fetching.
public func fetch() throws {
try fetchedResultsController.performFetch()
}
/// Retrieve the number of objects in a given section number.
/// - Parameter section: The section number to inquiry about.
/// - Returns: A number of objects in the given section number.
public func numberOfObjectsInSection(_ section: Int) -> Int {
guard
let sections = fetchedResultsController.sections,
sections.endIndex > section
else {
return 0
}
return sections[section].numberOfObjects
}
/// Retrieve an object out of a given index path.
/// - Parameter indexPath: The index path to use to retrieve an object.
/// - Returns: A `NSManagedObject` entity positioned in the given index path.
public func object(at indexPath: IndexPath) -> Model {
return fetchedResultsController.object(at: indexPath)
}
// MARK: NSFetchedResultsControllerDelegate
public func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
inProgressChanges.removeAll()
}
public func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
didChangePublisher.send(inProgressChanges)
}
public func controller(
_ controller: NSFetchedResultsController<NSFetchRequestResult>,
didChange sectionInfo: NSFetchedResultsSectionInfo,
atSectionIndex sectionIndex: Int,
for type: NSFetchedResultsChangeType
) {
if type == .insert {
inProgressChanges.append(.section(.inserted(sectionIndex)))
} else if type == .delete {
inProgressChanges.append(.section(.deleted(sectionIndex)))
}
}
public func controller(
_ controller: NSFetchedResultsController<NSFetchRequestResult>,
didChange anObject: Any,
at indexPath: IndexPath?,
for type: NSFetchedResultsChangeType,
newIndexPath: IndexPath?
) {
switch type {
case .insert:
guard let newIndexPath else { return }
inProgressChanges.append(.object(.inserted(at: newIndexPath)))
case .delete:
guard let indexPath else { return }
inProgressChanges.append(.object(.deleted(from: indexPath)))
case .move:
guard let indexPath, let newIndexPath else { return }
inProgressChanges.append(.object(.moved(from: indexPath, to: newIndexPath)))
case .update:
guard let indexPath else { return }
inProgressChanges.append(.object(.updated(at: indexPath)))
default:
break
}
}
}
// MARK: - Enumerations
public enum Change: Hashable {
public enum SectionUpdate: Hashable {
case inserted(Int)
case deleted(Int)
}
public enum ObjectUpdate: Hashable {
case inserted(at: IndexPath)
case deleted(from: IndexPath)
case updated(at: IndexPath)
case moved(from: IndexPath, to: IndexPath)
}
case section(SectionUpdate)
case object(ObjectUpdate)
}