From 2d91edd38bf1de992c304434363e5d8c60129a8f Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Wed, 12 Apr 2023 23:11:29 +0200 Subject: [PATCH] Implemented the LocationProvider provider in the Persistence library. --- .../Providers/LocationProvider.swift | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 Apps/Locations/Libraries/Sources/Persistence/Providers/LocationProvider.swift diff --git a/Apps/Locations/Libraries/Sources/Persistence/Providers/LocationProvider.swift b/Apps/Locations/Libraries/Sources/Persistence/Providers/LocationProvider.swift new file mode 100644 index 0000000..bc57917 --- /dev/null +++ b/Apps/Locations/Libraries/Sources/Persistence/Providers/LocationProvider.swift @@ -0,0 +1,149 @@ +// +// 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) +} +