// // LocationProvider.swift // Persistence // // Created by Javier Cicchelli on 12/04/2023. // Copyright © 2023 Röck+Cöde. All rights reserved. // import Combine import CoreData public class LocationProvider: NSObject { // MARK: Properties private let fetchedResultsController: NSFetchedResultsController /// The publisher that emits the changes detected to the Location entities in a given object context. public let didChangePublisher = PassthroughSubject<[Change], Never>() private var inProgressChanges: [Change] = [] /// The number of sections in the data. public var numberOfSections: Int { fetchedResultsController.sections?.count ?? 0 } // MARK: Initialisers /// Initialise this provider with the managed object context that would be used. /// - Parameter managedContext: A `NSManagedObjectContext` object context instance that will be used to provide entities. public init(managedContext: NSManagedObjectContext) { self.fetchedResultsController = .init( fetchRequest: .allLocations(), managedObjectContext: managedContext, sectionNameKeyPath: nil, cacheName: nil ) super.init() self.fetchedResultsController.delegate = self } // MARK: Functions /// Perform the fetching. public func fetch() throws { try fetchedResultsController.performFetch() } /// Retrieve the number of locations inside a given section number. /// - Parameter section: The section number to inquiry about. /// - Returns: A number of locations inside the given section number. public func numberOfLocationsInSection(_ section: Int) -> Int { guard let sections = fetchedResultsController.sections, sections.endIndex > section else { return 0 } return sections[section].numberOfObjects } /// Retrieve a location entity out of a given index path. /// - Parameter indexPath: The index path to which retrieve a location entity. /// - Returns: A `Location` entity positioned in the given index path. public func location(at indexPath: IndexPath) -> Location { return fetchedResultsController.object(at: indexPath) } } // MARK: - NSFetchedResultsControllerDelegate extension LocationProvider: NSFetchedResultsControllerDelegate { // MARK: Functions 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) }