Skip to content

Instantly share code, notes, and snippets.

@markbattistella
Last active February 25, 2023 00:48
Show Gist options
  • Save markbattistella/98be40ecf055cc51a0c5ac85e677eb51 to your computer and use it in GitHub Desktop.
Save markbattistella/98be40ecf055cc51a0c5ac85e677eb51 to your computer and use it in GitHub Desktop.
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