// // Persistence.swift // Shared // // Created by Trey Tartt on 1/5/22. // import CoreData import SwiftUI class PersistenceController { @AppStorage(UserDefaultsStore.Keys.useCloudKit.rawValue, store: GroupUserDefaults.groupDefaults) private var useCloudKit = false static let shared = PersistenceController.persistenceController private static var persistenceController: PersistenceController { return PersistenceController(inMemory: false) } public var viewContext: NSManagedObjectContext { return PersistenceController.shared.container.viewContext } public lazy var childContext: NSManagedObjectContext = { NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) }() public var switchContainerListeners = [(() -> Void)]() private var editedDataClosure = [() -> Void]() public var earliestEntry: MoodEntry? { let fetchRequest = NSFetchRequest(entityName: "MoodEntry") fetchRequest.sortDescriptors = [NSSortDescriptor(key: "forDate", ascending: true)] let first = try! viewContext.fetch(fetchRequest).first return first ?? nil } public var latestEntry: MoodEntry? { let fetchRequest = NSFetchRequest(entityName: "MoodEntry") fetchRequest.sortDescriptors = [NSSortDescriptor(key: "forDate", ascending: true)] let last = try! viewContext.fetch(fetchRequest).last return last ?? nil } lazy var container: NSPersistentContainer = { setupContainer() }() func switchContainer() { try? viewContext.save() container = setupContainer() for item in switchContainerListeners { item() } } public func addNewDataListener(closure: @escaping (() -> Void)) { editedDataClosure.append(closure) } public func runDataListeners() { for closure in editedDataClosure { closure() } } private func setupContainer() -> NSPersistentContainer { if useCloudKit { container = NSPersistentCloudKitContainer(name: "Feels") } else { container = NSCustomPersistentContainer(name: "Feels") } for description in container.persistentStoreDescriptions { description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey) description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey) description.setOption(true as NSNumber, forKey: NSMigratePersistentStoresAutomaticallyOption) description.setOption(true as NSNumber, forKey: NSInferMappingModelAutomaticallyOption) } container.loadPersistentStores(completionHandler: { (storeDescription, error) in self.container.viewContext.automaticallyMergesChangesFromParent = true if let error = error as NSError? { fatalError("Unresolved error \(error), \(error.userInfo)") } }) return container } init(inMemory: Bool = false) { container = setupContainer() } } extension NSManagedObjectContext { /// Executes the given `NSBatchDeleteRequest` and directly merges the changes to bring the given managed object context up to date. /// /// - Parameter batchDeleteRequest: The `NSBatchDeleteRequest` to execute. /// - Throws: An error if anything went wrong executing the batch deletion. public func executeAndMergeChanges(using batchDeleteRequest: NSBatchDeleteRequest) throws { batchDeleteRequest.resultType = .resultTypeObjectIDs let result = try execute(batchDeleteRequest) as? NSBatchDeleteResult let changes: [AnyHashable: Any] = [NSDeletedObjectsKey: result?.result as? [NSManagedObjectID] ?? []] NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [self]) } } class NSCustomPersistentContainer: NSPersistentContainer { override open class func defaultDirectoryURL() -> URL { #if DEBUG var storeURLDebug = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Constants.groupShareIdDebug) storeURLDebug = storeURLDebug?.appendingPathComponent("Feels-Debug.sqlite") return storeURLDebug! #endif var storeURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Constants.groupShareId) storeURL = storeURL?.appendingPathComponent("Feels.sqlite") return storeURL! } }