|
@@ -1,38 +1,121 @@
|
|
|
import CoreData
|
|
import CoreData
|
|
|
import Foundation
|
|
import Foundation
|
|
|
|
|
+import OSLog
|
|
|
|
|
|
|
|
class CoreDataStack: ObservableObject {
|
|
class CoreDataStack: ObservableObject {
|
|
|
- init() {}
|
|
|
|
|
-
|
|
|
|
|
static let shared = CoreDataStack()
|
|
static let shared = CoreDataStack()
|
|
|
static let identifier = "CoreDataStack"
|
|
static let identifier = "CoreDataStack"
|
|
|
|
|
|
|
|
|
|
+ private var notificationToken: NSObjectProtocol?
|
|
|
|
|
+ private let inMemory: Bool
|
|
|
|
|
+
|
|
|
|
|
+ private init(inMemory: Bool = false) {
|
|
|
|
|
+ self.inMemory = inMemory
|
|
|
|
|
+
|
|
|
|
|
+ // Observe Core Data remote change notifications on the queue where the changes were made
|
|
|
|
|
+ notificationToken = Foundation.NotificationCenter.default.addObserver(
|
|
|
|
|
+ forName: .NSPersistentStoreRemoteChange,
|
|
|
|
|
+ object: nil,
|
|
|
|
|
+ queue: nil
|
|
|
|
|
+ ) { _ in
|
|
|
|
|
+ debugPrint("Received a persistent store remote change notification")
|
|
|
|
|
+ Task {
|
|
|
|
|
+ await self.fetchPersistentHistory()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ deinit {
|
|
|
|
|
+ if let observer = notificationToken {
|
|
|
|
|
+ Foundation.NotificationCenter.default.removeObserver(observer)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// A persistent history token used for fetching transactions from the store
|
|
|
|
|
+ private var lastToken: NSPersistentHistoryToken?
|
|
|
|
|
+
|
|
|
|
|
+ /// A persistent container to set up the Core Data Stack
|
|
|
lazy var persistentContainer: NSPersistentContainer = {
|
|
lazy var persistentContainer: NSPersistentContainer = {
|
|
|
let container = NSPersistentContainer(name: "Core_Data")
|
|
let container = NSPersistentContainer(name: "Core_Data")
|
|
|
|
|
|
|
|
- container.loadPersistentStores(completionHandler: { _, error in
|
|
|
|
|
- guard let error = error as NSError? else { return }
|
|
|
|
|
- fatalError("Unresolved error: \(error), \(error.userInfo)")
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ guard let description = container.persistentStoreDescriptions.first else {
|
|
|
|
|
+ fatalError("Failed \(DebuggingIdentifiers.failed) to retrieve a persistent store description")
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
|
|
+ if inMemory {
|
|
|
|
|
+ description.url = URL(fileURLWithPath: "/dev/null")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Enable persistent store remote change notifications
|
|
|
|
|
+ /// - Tag: persistentStoreRemoteChange
|
|
|
|
|
+ description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
|
|
|
|
|
+
|
|
|
|
|
+ // Enable persistent history tracking
|
|
|
|
|
+ /// - Tag: persistentHistoryTracking
|
|
|
|
|
+ description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
|
|
|
|
|
+
|
|
|
|
|
+ container.loadPersistentStores { _, error in
|
|
|
|
|
+ if let error = error as NSError? {
|
|
|
|
|
+ fatalError("Unresolved Error \(DebuggingIdentifiers.failed) \(error), \(error.userInfo)")
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ container.viewContext.automaticallyMergesChangesFromParent = false
|
|
|
|
|
+ container.viewContext.name = "viewContext"
|
|
|
|
|
+ /// - Tag: viewContextmergePolicy
|
|
|
|
|
+ container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
|
|
|
|
|
+ container.viewContext.undoManager = nil
|
|
|
|
|
+ container.viewContext.shouldDeleteInaccessibleFaults = true
|
|
|
return container
|
|
return container
|
|
|
}()
|
|
}()
|
|
|
|
|
|
|
|
- // ensure thread safety by creating a NSManagedObjectContext for the main thread and for a background thread
|
|
|
|
|
- lazy var backgroundContext: NSManagedObjectContext = {
|
|
|
|
|
- let newbackgroundContext = CoreDataStack.shared.persistentContainer.newBackgroundContext()
|
|
|
|
|
- newbackgroundContext.automaticallyMergesChangesFromParent = true
|
|
|
|
|
- newbackgroundContext
|
|
|
|
|
- .mergePolicy =
|
|
|
|
|
- NSMergeByPropertyStoreTrumpMergePolicy // if two objects with the same unique constraint are found, overwrite with the object in the external storage
|
|
|
|
|
- return newbackgroundContext
|
|
|
|
|
- }()
|
|
|
|
|
|
|
+ /// Creates and configures a private queue context
|
|
|
|
|
+ private func newTaskContext() -> NSManagedObjectContext {
|
|
|
|
|
+ // Create a private queue context
|
|
|
|
|
+ /// - Tag: newBackgroundContext
|
|
|
|
|
+ let taskContext = persistentContainer.newBackgroundContext()
|
|
|
|
|
+ taskContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
|
|
|
|
|
+ taskContext.undoManager = nil
|
|
|
|
|
+ return taskContext
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- lazy var viewContext: NSManagedObjectContext = {
|
|
|
|
|
- let viewContext = CoreDataStack.shared.persistentContainer.viewContext
|
|
|
|
|
- viewContext.automaticallyMergesChangesFromParent = true
|
|
|
|
|
- return viewContext
|
|
|
|
|
- }()
|
|
|
|
|
|
|
+ func fetchPersistentHistory() async {
|
|
|
|
|
+ do {
|
|
|
|
|
+ try await fetchPersistentHistoryTransactionsAndChanges()
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ debugPrint("\(error.localizedDescription)")
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private func fetchPersistentHistoryTransactionsAndChanges() async throws {
|
|
|
|
|
+ let taskContext = newTaskContext()
|
|
|
|
|
+ taskContext.name = "persistentHistoryContext"
|
|
|
|
|
+ debugPrint("Start fetching persistent history changes from the store ... \(DebuggingIdentifiers.inProgress)")
|
|
|
|
|
+
|
|
|
|
|
+ try await taskContext.perform {
|
|
|
|
|
+ // Execute the persistent history change since the last transaction
|
|
|
|
|
+ /// - Tag: fetchHistory
|
|
|
|
|
+ let changeRequest = NSPersistentHistoryChangeRequest.fetchHistory(after: self.lastToken)
|
|
|
|
|
+ let historyResult = try taskContext.execute(changeRequest) as? NSPersistentHistoryResult
|
|
|
|
|
+ if let history = historyResult?.result as? [NSPersistentHistoryTransaction], !history.isEmpty {
|
|
|
|
|
+ self.mergePersistentHistoryChanges(from: history)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private func mergePersistentHistoryChanges(from history: [NSPersistentHistoryTransaction]) {
|
|
|
|
|
+ debugPrint("Received \(history.count) persistent history transactions")
|
|
|
|
|
+ // Update view context with objectIDs from history change request
|
|
|
|
|
+ /// - Tag: mergeChanges
|
|
|
|
|
+ let viewContext = persistentContainer.viewContext
|
|
|
|
|
+ viewContext.perform {
|
|
|
|
|
+ for transaction in history {
|
|
|
|
|
+ viewContext.mergeChanges(fromContextDidSave: transaction.objectIDNotification())
|
|
|
|
|
+ self.lastToken = transaction.token
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
// MARK: - Fetch Requests
|
|
// MARK: - Fetch Requests
|
|
|
|
|
|
|
@@ -49,7 +132,6 @@ class CoreDataStack: ObservableObject {
|
|
|
fetchLimit: Int? = nil,
|
|
fetchLimit: Int? = nil,
|
|
|
batchSize: Int? = nil,
|
|
batchSize: Int? = nil,
|
|
|
propertiesToFetch: [String]? = nil,
|
|
propertiesToFetch: [String]? = nil,
|
|
|
- context: NSManagedObjectContext? = CoreDataStack.shared.backgroundContext,
|
|
|
|
|
callingFunction: String = #function,
|
|
callingFunction: String = #function,
|
|
|
callingClass: String = #fileID
|
|
callingClass: String = #fileID
|
|
|
) -> [T] {
|
|
) -> [T] {
|
|
@@ -69,15 +151,19 @@ class CoreDataStack: ObservableObject {
|
|
|
request.resultType = .managedObjectResultType
|
|
request.resultType = .managedObjectResultType
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ let taskContext = newTaskContext()
|
|
|
|
|
+ taskContext.name = "fetchContext"
|
|
|
|
|
+ taskContext.transactionAuthor = "fetchEntities"
|
|
|
|
|
+
|
|
|
var result: [T]?
|
|
var result: [T]?
|
|
|
|
|
|
|
|
/// 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
|
|
/// 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
|
|
|
- context?.performAndWait {
|
|
|
|
|
|
|
+ taskContext.performAndWait {
|
|
|
do {
|
|
do {
|
|
|
debugPrint(
|
|
debugPrint(
|
|
|
"Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.succeeded) on Thread: \(Thread.current)"
|
|
"Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.succeeded) on Thread: \(Thread.current)"
|
|
|
)
|
|
)
|
|
|
- result = try context?.fetch(request)
|
|
|
|
|
|
|
+ result = try taskContext.fetch(request)
|
|
|
} catch let error as NSError {
|
|
} catch let error as NSError {
|
|
|
debugPrint(
|
|
debugPrint(
|
|
|
"Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.failed) \(error) on Thread: \(Thread.current)"
|
|
"Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.failed) \(error) on Thread: \(Thread.current)"
|
|
@@ -85,13 +171,55 @@ class CoreDataStack: ObservableObject {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// if result == nil {
|
|
|
|
|
-// debugPrint("Fetch result is nil in \(callingFunction) from \(callingClass) on thread \(Thread.current)")
|
|
|
|
|
-// } else {
|
|
|
|
|
-// debugPrint(
|
|
|
|
|
-// "Fetch result count: \(result?.count ?? 0) in \(callingFunction) from \(callingClass) on thread \(Thread.current)"
|
|
|
|
|
-// )
|
|
|
|
|
-// }
|
|
|
|
|
|
|
+ return result ?? []
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ func fetchEntities2<T: NSManagedObject>(
|
|
|
|
|
+ ofType type: T.Type,
|
|
|
|
|
+ onContext context: NSManagedObjectContext,
|
|
|
|
|
+ predicate: NSPredicate,
|
|
|
|
|
+ key: String,
|
|
|
|
|
+ ascending: Bool,
|
|
|
|
|
+ fetchLimit: Int? = nil,
|
|
|
|
|
+ batchSize: Int? = nil,
|
|
|
|
|
+ propertiesToFetch: [String]? = nil,
|
|
|
|
|
+ callingFunction: String = #function,
|
|
|
|
|
+ callingClass: String = #fileID
|
|
|
|
|
+ ) -> [T] {
|
|
|
|
|
+ let request = NSFetchRequest<T>(entityName: String(describing: type))
|
|
|
|
|
+ request.sortDescriptors = [NSSortDescriptor(key: key, ascending: ascending)]
|
|
|
|
|
+ request.predicate = predicate
|
|
|
|
|
+ if let limit = fetchLimit {
|
|
|
|
|
+ request.fetchLimit = limit
|
|
|
|
|
+ }
|
|
|
|
|
+ if let batchSize = batchSize {
|
|
|
|
|
+ request.fetchBatchSize = batchSize
|
|
|
|
|
+ }
|
|
|
|
|
+ if let propertiesTofetch = propertiesToFetch {
|
|
|
|
|
+ request.propertiesToFetch = propertiesTofetch
|
|
|
|
|
+ request.resultType = .managedObjectResultType
|
|
|
|
|
+ } else {
|
|
|
|
|
+ request.resultType = .managedObjectResultType
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ context.name = "fetchContext"
|
|
|
|
|
+ context.transactionAuthor = "fetchEntities"
|
|
|
|
|
+
|
|
|
|
|
+ var result: [T]?
|
|
|
|
|
+
|
|
|
|
|
+ /// 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
|
|
|
|
|
+ context.performAndWait {
|
|
|
|
|
+ do {
|
|
|
|
|
+ debugPrint(
|
|
|
|
|
+ "Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.succeeded) on Thread: \(Thread.current)"
|
|
|
|
|
+ )
|
|
|
|
|
+ result = try context.fetch(request)
|
|
|
|
|
+ } catch let error as NSError {
|
|
|
|
|
+ debugPrint(
|
|
|
|
|
+ "Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.failed) \(error) on Thread: \(Thread.current)"
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
return result ?? []
|
|
return result ?? []
|
|
|
}
|
|
}
|
|
@@ -124,17 +252,21 @@ class CoreDataStack: ObservableObject {
|
|
|
request.propertiesToFetch = propertiesToFetch
|
|
request.propertiesToFetch = propertiesToFetch
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ let taskContext = newTaskContext()
|
|
|
|
|
+ taskContext.name = "fetchContext"
|
|
|
|
|
+ taskContext.transactionAuthor = "fetchEntities"
|
|
|
|
|
+
|
|
|
// perform fetch in the background
|
|
// perform fetch in the background
|
|
|
//
|
|
//
|
|
|
// the fetch returns a NSManagedObjectID which can be safely passed to the main queue because they are thread safe
|
|
// the fetch returns a NSManagedObjectID which can be safely passed to the main queue because they are thread safe
|
|
|
- backgroundContext.perform {
|
|
|
|
|
|
|
+ taskContext.perform {
|
|
|
var result: [NSManagedObjectID]?
|
|
var result: [NSManagedObjectID]?
|
|
|
|
|
|
|
|
do {
|
|
do {
|
|
|
debugPrint(
|
|
debugPrint(
|
|
|
"Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.succeeded) on thread \(Thread.current)"
|
|
"Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.succeeded) on thread \(Thread.current)"
|
|
|
)
|
|
)
|
|
|
- result = try self.backgroundContext.fetch(request)
|
|
|
|
|
|
|
+ result = try taskContext.fetch(request)
|
|
|
} catch let error as NSError {
|
|
} catch let error as NSError {
|
|
|
debugPrint(
|
|
debugPrint(
|
|
|
"Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.failed) \(error) on thread \(Thread.current)"
|
|
"Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.failed) \(error) on thread \(Thread.current)"
|
|
@@ -148,7 +280,7 @@ class CoreDataStack: ObservableObject {
|
|
|
"Returning fetch result to main thread in \(callingFunction) from \(callingClass) on thread \(Thread.current)"
|
|
"Returning fetch result to main thread in \(callingFunction) from \(callingClass) on thread \(Thread.current)"
|
|
|
)
|
|
)
|
|
|
// Convert NSManagedObjectIDs to objects in the main context
|
|
// Convert NSManagedObjectIDs to objects in the main context
|
|
|
- let mainContext = self.viewContext
|
|
|
|
|
|
|
+ let mainContext = CoreDataStack.shared.persistentContainer.viewContext
|
|
|
let mainContextObjects = result.compactMap { mainContext.object(with: $0) as? T }
|
|
let mainContextObjects = result.compactMap { mainContext.object(with: $0) as? T }
|
|
|
completion(mainContextObjects)
|
|
completion(mainContextObjects)
|
|
|
} else {
|
|
} else {
|
|
@@ -186,15 +318,19 @@ class CoreDataStack: ObservableObject {
|
|
|
request.propertiesToFetch = propertiesToFetch
|
|
request.propertiesToFetch = propertiesToFetch
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ let taskContext = newTaskContext()
|
|
|
|
|
+ taskContext.name = "fetchContext"
|
|
|
|
|
+ taskContext.transactionAuthor = "fetchEntities"
|
|
|
|
|
+
|
|
|
// Perform fetch in the background
|
|
// Perform fetch in the background
|
|
|
- backgroundContext.perform {
|
|
|
|
|
|
|
+ taskContext.perform {
|
|
|
var result: [NSManagedObjectID]?
|
|
var result: [NSManagedObjectID]?
|
|
|
|
|
|
|
|
do {
|
|
do {
|
|
|
debugPrint(
|
|
debugPrint(
|
|
|
"Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.succeeded) on thread \(Thread.current)"
|
|
"Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.succeeded) on thread \(Thread.current)"
|
|
|
)
|
|
)
|
|
|
- result = try self.backgroundContext.fetch(request)
|
|
|
|
|
|
|
+ result = try taskContext.fetch(request)
|
|
|
} catch let error as NSError {
|
|
} catch let error as NSError {
|
|
|
debugPrint(
|
|
debugPrint(
|
|
|
"Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.failed) \(error) on thread \(Thread.current)"
|
|
"Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.failed) \(error) on thread \(Thread.current)"
|
|
@@ -211,12 +347,12 @@ class CoreDataStack: ObservableObject {
|
|
|
// takes a context as a parameter to be executed either on the main thread or on a background thread
|
|
// takes a context as a parameter to be executed either on the main thread or on a background thread
|
|
|
// save on the thread of the backgroundContext
|
|
// save on the thread of the backgroundContext
|
|
|
func saveContext(useViewContext: Bool = false, callingFunction: String = #function, callingClass: String = #fileID) throws {
|
|
func saveContext(useViewContext: Bool = false, callingFunction: String = #function, callingClass: String = #fileID) throws {
|
|
|
- let contextToUse = useViewContext ? viewContext : backgroundContext
|
|
|
|
|
|
|
+ let contextToUse = useViewContext ? CoreDataStack.shared.persistentContainer.viewContext : newTaskContext()
|
|
|
|
|
|
|
|
try contextToUse.performAndWait {
|
|
try contextToUse.performAndWait {
|
|
|
if contextToUse.hasChanges {
|
|
if contextToUse.hasChanges {
|
|
|
do {
|
|
do {
|
|
|
- try self.backgroundContext.save()
|
|
|
|
|
|
|
+ try contextToUse.save()
|
|
|
debugPrint(
|
|
debugPrint(
|
|
|
"Saving to Core Data successful in \(callingFunction) in \(callingClass): \(DebuggingIdentifiers.succeeded)"
|
|
"Saving to Core Data successful in \(callingFunction) in \(callingClass): \(DebuggingIdentifiers.succeeded)"
|
|
|
)
|
|
)
|
|
@@ -229,4 +365,46 @@ class CoreDataStack: ObservableObject {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ // MARK: - Delete
|
|
|
|
|
+
|
|
|
|
|
+ //
|
|
|
|
|
+ /// Synchronously delete entries with specified object IDs
|
|
|
|
|
+ func deleteObject(identifiedBy objectIDs: [NSManagedObjectID]) {
|
|
|
|
|
+ let viewContext = persistentContainer.viewContext
|
|
|
|
|
+ debugPrint("Start deleting data from the store ...\(DebuggingIdentifiers.inProgress)")
|
|
|
|
|
+
|
|
|
|
|
+ viewContext.perform {
|
|
|
|
|
+ objectIDs.forEach { objectID in
|
|
|
|
|
+ let entryToDelete = viewContext.object(with: objectID)
|
|
|
|
|
+ viewContext.delete(entryToDelete)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ debugPrint("Successfully deleted data. \(DebuggingIdentifiers.succeeded)")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// Asynchronously deletes records
|
|
|
|
|
+// func batchDelete<T: NSManagedObject>(_ objects: [T]) async throws {
|
|
|
|
|
+// let objectIDs = objects.map(\.objectID)
|
|
|
|
|
+// let taskContext = newTaskContext()
|
|
|
|
|
+// // Add name and author to identify source of persistent history changes.
|
|
|
|
|
+// taskContext.name = "deleteContext"
|
|
|
|
|
+// taskContext.transactionAuthor = "batchDelete"
|
|
|
|
|
+// debugPrint("Start deleting data from the store... \(DebuggingIdentifiers.inProgress)")
|
|
|
|
|
+//
|
|
|
|
|
+// try await taskContext.perform {
|
|
|
|
|
+// // Execute the batch delete.
|
|
|
|
|
+// let batchDeleteRequest = NSBatchDeleteRequest(objectIDs: objectIDs)
|
|
|
|
|
+// guard let fetchResult = try? taskContext.execute(batchDeleteRequest),
|
|
|
|
|
+// let batchDeleteResult = fetchResult as? NSBatchDeleteResult,
|
|
|
|
|
+// let success = batchDeleteResult.result as? Bool, success
|
|
|
|
|
+// else {
|
|
|
|
|
+// debugPrint("Failed to execute batch delete request \(DebuggingIdentifiers.failed)")
|
|
|
|
|
+// throw CoreDataError.batchDeleteError
|
|
|
|
|
+// }
|
|
|
|
|
+// }
|
|
|
|
|
+//
|
|
|
|
|
+// debugPrint("Successfully deleted data. \(DebuggingIdentifiers.succeeded)")
|
|
|
|
|
+// }
|
|
|
}
|
|
}
|