Last active
February 25, 2023 00:48
-
-
Save markbattistella/98be40ecf055cc51a0c5ac85e677eb51 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
final class CoreDataManager { | |
// -- singleton initalisation | |
static let shared = CoreDataManager() | |
var container: NSPersistentContainer | |
// -- managed object context | |
var managedObjectContext: NSManagedObjectContext { | |
container.viewContext | |
} | |
// -- initialiser | |
private init() { | |
// -- get the model | |
container = NSPersistentCloudKitContainer(name: "AttendanceModel") | |
// -- get the description | |
guard let description = container.persistentStoreDescriptions.first else { | |
fatalError("###\(#function): Failed to retrieve a persistent store description.") | |
} | |
// -- listen to changes from cloudkit | |
description.setOption( | |
true as NSNumber, | |
forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey | |
) | |
container.loadPersistentStores { storeDescription, error in | |
if let error = error as NSError? { | |
fatalError("Unresolved error \(error), \(error.userInfo)") | |
} | |
storeDescription.shouldInferMappingModelAutomatically = false | |
} | |
managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy | |
managedObjectContext.automaticallyMergesChangesFromParent = true | |
} | |
// MARK: - CRUD operations (create / fetch / update / delete) | |
/// Creates a new managed object of the specified type and executes the | |
/// completion block with the new object. | |
/// - Parameters: | |
/// - objectType: The type of the managed object to create. | |
/// - completion: A block that takes the new managed object as its parameter. | |
func create<T: NSManagedObject>(_ objectType: T.Type, completion: (T) -> Void) throws { | |
let newObject = NSEntityDescription.insertNewObject( | |
forEntityName: String(describing: objectType), | |
into: managedObjectContext | |
) as! T | |
completion(newObject) | |
try save() | |
} | |
/// Fetches a list of `objectType` objects from the Core Data store. | |
/// - Parameters: | |
/// - objectType: The type of object to fetch. | |
/// - predicate: An optional predicate for filtering the results. | |
/// - sortDescriptors: An optional list of sort descriptors for sorting the results. | |
/// - Returns: An array of `objectType` objects. | |
func fetch<T: NSManagedObject>(_ objectType: T.Type, | |
predicate: NSPredicate? = nil, | |
sortDescriptors: [NSSortDescriptor]? = nil) throws -> [T] { | |
let fetchRequest = NSFetchRequest<T>(entityName: String(describing: objectType)) | |
fetchRequest.predicate = predicate | |
fetchRequest.sortDescriptors = sortDescriptors | |
do { | |
let objects = try managedObjectContext.fetch(fetchRequest) | |
return objects | |
} catch { | |
throw CoreDataError.unableToFetch | |
} | |
} | |
/// Saves the managed object context. | |
/// - Parameter completion: A completion block that is called after the save is | |
/// complete. The block takes an optional Error as its only argument. | |
func save() throws { | |
guard managedObjectContext.hasChanges else { return } | |
do { | |
try managedObjectContext.save() | |
} catch { | |
managedObjectContext.rollback() | |
throw CoreDataError.unableToSave | |
} | |
} | |
/// Deletes an NSManagedObject from the managed object context. | |
/// - Parameters: | |
/// - object: The NSManagedObject to delete. | |
/// - completion: A closure to be called after the delete operation is completed. | |
/// If the delete operation was successful, the error parameter is nil. If the | |
/// delete operation failed, the error parameter contains the error that occurred. | |
func delete<T: NSManagedObject>(_ object: T) throws { | |
managedObjectContext.delete(object) | |
try save() | |
} | |
/// Deletes all objects of a given type that match the specified predicate. | |
/// - Parameters: | |
/// - objectType: The type of object to delete. | |
/// - predicate: An optional predicate to limit the objects that will be deleted. If `nil`, all objects of the | |
/// given type will be deleted. | |
/// - Throws: An error if any of the objects could not be deleted. | |
func deleteAll<T: NSManagedObject>(_ objectType: T.Type, predicate: NSPredicate? = nil) throws { | |
let objects = try fetch(objectType, predicate: predicate) | |
for object in objects { | |
try delete(object) | |
} | |
} | |
// MARK: - iCloud functionality | |
/// Determines if the user is logged into an Apple ID and using iCloud. | |
/// - Returns: A boolean indicating if the user is logged into an Apple ID and using iCloud. | |
func canUseCloudFunctionality() -> Bool { | |
isLoggedIntoAppleID() && isUsingiCloud() | |
} | |
/// Determines whether the user is currently using iCloud or not. | |
/// - Returns: `true` if the user is using iCloud, `false` otherwise. | |
private func isUsingiCloud() -> Bool { | |
let iCloudAccountStatus = FileManager.default.ubiquityIdentityToken | |
return iCloudAccountStatus != nil | |
} | |
/// Determines whether the user is logged into an Apple ID. | |
/// - Remark: | |
/// - `available`: user is logged in to an iCloud account and iCloud is enabled | |
/// - `noAccount`: user is not logged in to an iCloud account or iCloud is not enabled | |
/// - `restricted`: the device is in a restricted or parental controlled environment | |
/// - `couldNotDetermine`: an error occurred trying to determine the user's iCloud account status | |
/// - `temporarilyUnavailable`: user’s iCloud account is available, but isn’t ready | |
/// to support CloudKit operation | |
/// - Returns: A boolean value indicating whether the user is logged into an Apple ID. | |
private func isLoggedIntoAppleID() -> Bool { | |
let container = CKContainer.default() | |
var status: Bool = false | |
container.accountStatus { (accountStatus, error) in | |
switch accountStatus { | |
case .available: | |
status = true | |
case .noAccount, | |
.restricted, | |
.couldNotDetermine, | |
.temporarilyUnavailable: | |
status = false | |
@unknown default: | |
status = false | |
} | |
} | |
return status | |
} | |
/// Syncs the local data store with the cloud data store. | |
/// - Parameter pull: A boolean value indicating whether to pull data from the | |
/// cloud (true) or push data to the cloud (false). | |
func syncWithCloud(pull: Bool) async throws { | |
if canUseCloudFunctionality() { | |
try await container.viewContext.perform { | |
do { | |
if pull { | |
self.managedObjectContext.refreshAllObjects() | |
} else { | |
try self.managedObjectContext.save() | |
} | |
} catch { | |
throw CoreDataError.errorSyncing | |
} | |
} | |
} else { | |
throw CoreDataError.noCloudFunctionality | |
} | |
} | |
/// Forces a sync with the cloud. | |
/// - Parameter pull: A boolean value indicating whether to pull data from the cloud. | |
func forceSyncWithCloud(pull: Bool) async throws { | |
if canUseCloudFunctionality() { | |
try await container.viewContext.perform { | |
do { | |
self.managedObjectContext.reset() | |
if pull { | |
self.managedObjectContext.refreshAllObjects() | |
} else { | |
try self.managedObjectContext.save() | |
} | |
} catch { | |
throw CoreDataError.errorSyncing | |
} | |
} | |
} | |
} | |
/// This function toggles cloud sync for Core Data. | |
/// - Parameters: | |
/// - isEnabled: A boolean value indicating whether cloud sync should be enabled or disabled. | |
/// - Throws: CoreDataError.noCloudFunctionality or CoreDataError.persistentStoreError | |
func toggleCloudSync(isEnabled: Bool) throws { | |
guard canUseCloudFunctionality() else { | |
throw CoreDataError.noCloudFunctionality | |
} | |
guard let description = container.persistentStoreDescriptions.first else { | |
return | |
} | |
description.setOption( | |
isEnabled as NSNumber, | |
forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey | |
) | |
do { | |
try container.persistentStoreCoordinator.performAndWait { | |
try container.persistentStoreCoordinator.remove( | |
container.persistentStoreCoordinator.persistentStores.first! | |
) | |
try container.persistentStoreCoordinator.addPersistentStore( | |
ofType: NSSQLiteStoreType, | |
configurationName: nil, | |
at: description.url, | |
options: description.options | |
) | |
} | |
} catch { | |
throw CoreDataError.persistentStoreError(error: error) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment