From 879b6d6bbd7cca00b6a5507bbe8bc8f9c8497bd2 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Mon, 17 Apr 2023 00:11:05 +0200 Subject: [PATCH] Implemented the Fetcher class. --- Sources/Persistence/Classes/Fetcher.swift | 154 ++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 Sources/Persistence/Classes/Fetcher.swift diff --git a/Sources/Persistence/Classes/Fetcher.swift b/Sources/Persistence/Classes/Fetcher.swift new file mode 100644 index 0000000..1afcaa8 --- /dev/null +++ b/Sources/Persistence/Classes/Fetcher.swift @@ -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: 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 + + /// 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, + 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) { + inProgressChanges.removeAll() + } + + public func controllerDidChangeContent(_ controller: NSFetchedResultsController) { + didChangePublisher.send(inProgressChanges) + } + + public func controller( + _ controller: NSFetchedResultsController, + 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, + 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) +}