CoreDataStack.swift 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import CoreData
  2. import Foundation
  3. class CoreDataStack: ObservableObject {
  4. init() {}
  5. static let shared = CoreDataStack()
  6. static let identifier = "CoreDataStack"
  7. lazy var persistentContainer: NSPersistentContainer = {
  8. let container = NSPersistentContainer(name: "Core_Data")
  9. container.loadPersistentStores(completionHandler: { _, error in
  10. guard let error = error as NSError? else { return }
  11. fatalError("Unresolved error: \(error), \(error.userInfo)")
  12. })
  13. return container
  14. }()
  15. // ensure thread safety by creating a NSManagedObjectContext for the main thread and for a background thread
  16. lazy var backgroundContext: NSManagedObjectContext = {
  17. let newbackgroundContext = CoreDataStack.shared.persistentContainer.newBackgroundContext()
  18. newbackgroundContext.automaticallyMergesChangesFromParent = true
  19. newbackgroundContext
  20. .mergePolicy =
  21. NSMergeByPropertyStoreTrumpMergePolicy // if two objects with the same unique constraint are found, overwrite with the object in the external storage
  22. return newbackgroundContext
  23. }()
  24. lazy var viewContext: NSManagedObjectContext = {
  25. let viewContext = CoreDataStack.shared.persistentContainer.viewContext
  26. viewContext.automaticallyMergesChangesFromParent = true
  27. return viewContext
  28. }()
  29. // fetch on the thread of the backgroundContext
  30. func fetchEntities<T: NSManagedObject>(
  31. ofType type: T.Type,
  32. predicate: NSPredicate,
  33. key: String,
  34. ascending: Bool,
  35. fetchLimit: Int? = nil,
  36. batchSize: Int? = nil,
  37. propertiesToFetch: [String]? = nil,
  38. callingFunction: String = #function,
  39. callingClass: String = #fileID
  40. ) -> [T] {
  41. let request = NSFetchRequest<T>(entityName: String(describing: type))
  42. request.sortDescriptors = [NSSortDescriptor(key: key, ascending: ascending)]
  43. request.predicate = predicate
  44. if let limit = fetchLimit {
  45. request.fetchLimit = limit
  46. }
  47. if let batchSize = batchSize {
  48. request.fetchBatchSize = batchSize
  49. }
  50. if let propertiesTofetch = propertiesToFetch {
  51. request.propertiesToFetch = propertiesTofetch
  52. request.resultType = .managedObjectResultType
  53. } else {
  54. request.resultType = .managedObjectResultType
  55. }
  56. var result: [T]?
  57. /// we need to ensure that the fetch immediately returns a value as long as the whole app does not use the async await pattern, otherwise we could perform this asynchronously with backgroundContext.perform and not block the thread
  58. backgroundContext.performAndWait {
  59. do {
  60. debugPrint("Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.succeeded)")
  61. result = try self.backgroundContext.fetch(request)
  62. } catch let error as NSError {
  63. debugPrint(
  64. "Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.failed) \(error)"
  65. )
  66. }
  67. }
  68. return result ?? []
  69. }
  70. // save on the thread of the backgroundContext
  71. func saveContext(useViewContext: Bool = false, callingFunction: String = #function, callingClass: String = #fileID) throws {
  72. let contextToUse = useViewContext ? viewContext : backgroundContext
  73. try contextToUse.performAndWait {
  74. if contextToUse.hasChanges {
  75. do {
  76. try self.backgroundContext.save()
  77. debugPrint(
  78. "Saving to Core Data successful in \(callingFunction) in \(callingClass): \(DebuggingIdentifiers.succeeded)"
  79. )
  80. } catch let error as NSError {
  81. debugPrint(
  82. "Saving to Core Data failed in \(callingFunction) in \(callingClass): \(DebuggingIdentifiers.failed) with error \(error), \(error.userInfo)"
  83. )
  84. throw error
  85. }
  86. }
  87. }
  88. }
  89. }