Skip to content

Instantly share code, notes, and snippets.

@markbattistella
Last active February 25, 2023 00:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save markbattistella/98be40ecf055cc51a0c5ac85e677eb51 to your computer and use it in GitHub Desktop.
Save markbattistella/98be40ecf055cc51a0c5ac85e677eb51 to your computer and use it in GitHub Desktop.
import CoreData
import CloudKit
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: "Core Data")
// -- 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, saveError: @escaping (Error?) -> Void) {
let newObject = NSEntityDescription.insertNewObject(
forEntityName: String(describing: objectType),
into: managedObjectContext
) as! T
completion(newObject)
self.save(completion: saveError)
}
/// 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) -> [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 {
print("[x] Error: \(error.localizedDescription)")
return []
}
}
/// 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(completion: @escaping (Error?) -> Void = { _ in }) {
if managedObjectContext.hasChanges {
do {
try managedObjectContext.save()
} catch {
completion(error)
managedObjectContext.rollback()
}
} else {
completion(nil)
}
}
/// 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, completion: @escaping (Error?) -> Void = { _ in }) {
managedObjectContext.delete(object)
save(completion: completion)
}
/// Deletes all the given objects from the managed object context.
/// - Parameter objects: An array of NSManagedObject instances to delete.
func deleteAll<T: NSManagedObject>(_ objects: [T]) {
for object in objects {
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) {
if canUseCloudFunctionality() {
container.viewContext.perform {
do {
if pull {
self.managedObjectContext.refreshAllObjects()
} else {
try self.managedObjectContext.save()
}
} catch {
print("Error syncing with cloud: \(error)")
}
}
} else {
print("Error: User is not logged into an Apple ID or not using iCloud")
}
}
/// Forces a sync with the cloud.
/// - Parameter pull: A boolean value indicating whether to pull data from the cloud.
func forceSyncWithCloud(pull: Bool) {
if canUseCloudFunctionality() {
container.viewContext.perform {
do {
self.managedObjectContext.reset()
if pull {
self.managedObjectContext.refreshAllObjects()
} else {
try self.managedObjectContext.save()
}
} catch {
print("Error force syncing with cloud: \(error)")
}
}
}
}
/// Toggles the CloudKit synchronization for the Core Data stack.
/// - Parameter isEnabled: A boolean value indicating whether CloudKit synchronization should be enabled or disabled.
func toggleCloudSync(isEnabled: Bool) {
if !canUseCloudFunctionality() {
print("Cannot use cloud functionality")
return
}
guard let description = container.persistentStoreDescriptions.first else {
return
}
description.setOption(isEnabled as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
container.persistentStoreCoordinator.performAndWait {
do {
try container.persistentStoreCoordinator.remove(container.persistentStoreCoordinator.persistentStores.first!)
try container.persistentStoreCoordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: description.url, options: description.options)
} catch {
print(error)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment