Last active
February 2, 2020 05:46
-
-
Save SergLam/ae11e60572f2ec2fc6b054034f6d8253 to your computer and use it in GitHub Desktop.
CoreData manager basic setup
This file contains hidden or 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
import CoreData | |
import Foundation | |
extension CoreDataManager { | |
/** | |
User profile (id, email, phone, picture_url, social links and ect.) | |
*/ | |
var myProfileSettings: MyProfileSettings? { | |
get { | |
let request: NSFetchRequest<MyProfileSettings> = MyProfileSettings.fetchRequest() | |
do { | |
let settingsArray = try viewContext.fetch(request) | |
return settingsArray.first | |
} catch { | |
assertionFailure(error.localizedDescription) | |
return nil | |
} | |
} | |
set { | |
guard newValue != nil else { | |
CoreDataManager.shared.deleteAll(MyProfileSettings.self) { success, message in | |
guard success else { | |
assertionFailure(message) | |
return | |
} | |
} | |
return | |
} | |
CoreDataManager.shared.saveContext { _ in } | |
} | |
} | |
/** | |
User app settings (mapStyle, offlineMaps, measurmentUnit, notifications) | |
*/ | |
var myUserSettings: UserSettings? { | |
get { | |
let request: NSFetchRequest<UserSettings> = UserSettings.fetchRequest() | |
do { | |
let settingsArray = try viewContext.fetch(request) | |
return settingsArray.first | |
} catch { | |
assertionFailure(error.localizedDescription) | |
return nil | |
} | |
} | |
set { | |
guard newValue != nil else { | |
CoreDataManager.shared.deleteAll(UserSettings.self) { success, message in | |
guard success else { | |
assertionFailure(message) | |
return | |
} | |
} | |
return | |
} | |
CoreDataManager.shared.saveContext { _ in } | |
} | |
} | |
} |
This file contains hidden or 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
import CoreData | |
import Foundation | |
// TODO: Add generics here: | |
// https://medium.com/better-programming/reusable-generic-database-layer-in-swift-7950d604883b | |
typealias OperationResult = ((Bool, String) -> Void) | |
final class CoreDataManager: NSObject { | |
static let shared = CoreDataManager(modelFileName: "DatabaseScheme"){} | |
private static let defaultModelName = "DatabaseScheme" | |
private(set) var modelName: String | |
private(set) var inMemory: Bool | |
private var coordinator: NSPersistentStoreCoordinator | |
private var model: NSManagedObjectModel | |
var context: NSManagedObjectContext | |
var container: NSPersistentContainer | |
// NOTE: queue for database operations from background thread | |
// See ParseOperation.swift | |
private let queue: OperationQueue | |
func initialize() { | |
// NOTE: stub method to awake code data setup | |
// AppDelegate -> didFinishLaunch -> CoreDataManager.shared.initialize() | |
} | |
func addOperation(_ operation: CoreDataOperation) { | |
queue.addOperation(operation) | |
} | |
init(modelFileName: String = CoreDataManager.defaultModelName, | |
inMemory: Bool = false, | |
completion: @escaping () -> Void) { | |
guard let modelURL = Bundle.main.url(forResource: modelFileName, withExtension: "momd") else { | |
preconditionFailure("Unable to find specified database model file") | |
} | |
guard let model = NSManagedObjectModel(contentsOf: modelURL) else { | |
preconditionFailure("Unable to find specified database model file") | |
} | |
self.queue = OperationQueue() | |
queue.maxConcurrentOperationCount = 1 | |
self.modelName = modelFileName | |
self.model = model | |
self.inMemory = inMemory | |
coordinator = NSPersistentStoreCoordinator(managedObjectModel: model) | |
context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) | |
context.persistentStoreCoordinator = coordinator | |
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) | |
let docURL = urls[urls.endIndex - 1] | |
/* The directory the application uses to store the Core Data store file. | |
This code uses a file named "DataModel.sqlite" in the application's documents directory. | |
*/ | |
let storeURL = docURL.appendingPathComponent("Database.sqlite") | |
debugPrint("STORE: \(storeURL)") | |
do { | |
// Request Lightweight Migration if needed options | |
// https://developer.apple.com/documentation/coredata/using_lightweight_migration | |
let options = [NSMigratePersistentStoresAutomaticallyOption: true, | |
NSInferMappingModelAutomaticallyOption: true] | |
try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: options) | |
} catch { | |
fatalError("Error migrating store: \(error)") | |
} | |
container = NSPersistentContainer(name: modelName) | |
let description = NSPersistentStoreDescription() | |
if inMemory { | |
description.type = NSInMemoryStoreType | |
description.shouldAddStoreAsynchronously = false | |
container.persistentStoreDescriptions = [description] | |
} else { | |
description.type = NSSQLiteStoreType | |
description.shouldAddStoreAsynchronously = false | |
container.persistentStoreDescriptions = [description] | |
} | |
container.loadPersistentStores { _, error in | |
if let error = error as NSError? { | |
fatalError("Failed to load Core Data stack: \(error)") | |
} | |
completion() | |
} | |
} | |
// MARK: - Core Data Saving support | |
func saveContext(completion: @escaping FirClosure) { | |
guard context.hasChanges else { | |
completion(.success(())) | |
return | |
} | |
do { | |
try context.save() | |
completion(.success(())) | |
} catch { | |
assertionFailure(error.localizedDescription) | |
completion(.failure(error)) | |
} | |
} | |
} | |
// MARK: - Public properties | |
extension CoreDataManager { | |
var viewContext: NSManagedObjectContext { | |
return container.viewContext | |
} | |
var persistentStoreCoordinator: NSPersistentStoreCoordinator { | |
return container.persistentStoreCoordinator | |
} | |
} | |
// MARK: - CRUD core data operations | |
extension CoreDataManager { | |
// MARK: create and update opetations | |
func write<T: NSManagedObject>(shouldUpdate: Bool, entities: [T], completion: @escaping (Bool) -> Void) { | |
saveContext(completion: { result in | |
switch result { | |
case .success: | |
completion(true) | |
case .failure: | |
completion(false) | |
} | |
}) | |
} | |
// MARK: read operations | |
func fetchObjects<T: NSManagedObject>(_ fieldName: String, _ fieldValue: Any, _ entity: T.Type) -> [T]? { | |
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: entity.entity().managedObjectClassName) | |
fetchRequest.predicate = NSPredicate(format: "\(fieldName) = %@", argumentArray: [fieldValue]) | |
do { | |
let objects = try context.fetch(fetchRequest) | |
return objects as? [T] | |
} catch { | |
assertionFailure(error.localizedDescription) | |
return nil | |
} | |
} | |
func readAllObjects<T: NSManagedObject>(_ entity: T.Type) -> [T] { | |
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: T.entity().managedObjectClassName) | |
do { | |
let objects = try context.fetch(fetchRequest) as? [T] ?? [] | |
return objects | |
} catch { | |
assertionFailure(error.localizedDescription) | |
return [] | |
} | |
} | |
// MARK: delete operations | |
func deleteObjects<T: NSManagedObject>(_ fieldName: String, _ fieldValue: Any, _ entity: T.Type, completion: @escaping OperationResult) { | |
guard let objects = fetchObjects(fieldName, fieldValue, entity) else { | |
completion(false, "Objects with specified parameters not found") | |
return | |
} | |
objects.forEach { context.delete($0) } | |
saveContext(completion: { result in | |
switch result { | |
case .success: | |
completion(true, "Delete operation completed successfully") | |
case .failure(let error): | |
completion(false, error.localizedDescription) | |
} | |
}) | |
} | |
func deleteAll<T: NSManagedObject>(_ objectsToDelete: T.Type, completion: @escaping OperationResult) { | |
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: T.entity().managedObjectClassName) | |
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) | |
do { | |
try coordinator.execute(deleteRequest, with: context) | |
saveContext { result in | |
switch result { | |
case .success: | |
completion(true, "Objects deleted successfully") | |
case .failure(let error): | |
completion(false, error.localizedDescription) | |
} | |
} | |
} catch { | |
assertionFailure(error.localizedDescription) | |
completion(false, error.localizedDescription) | |
} | |
} | |
} |
This file contains hidden or 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
import CoreData | |
import Foundation | |
final class CoreDataOperation: Operation { | |
let viewContext: NSManagedObjectContext | |
let parseContext: NSManagedObjectContext | |
typealias ParsingHandler = (_ context: NSManagedObjectContext) -> Void | |
var parsingHandler: ParsingHandler | |
@discardableResult | |
init(for database: CoreDataManager = .shared, parsingHandler: @escaping ParsingHandler) { | |
self.parsingHandler = parsingHandler | |
viewContext = database.viewContext | |
parseContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) | |
super.init() | |
parseContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy | |
parseContext.parent = CoreDataManager.shared.context | |
NotificationCenter.default.addObserver(self, selector: #selector(managedObjectContextDidSave(_:)), name: .NSManagedObjectContextDidSave, object: parseContext) | |
} | |
deinit { | |
NotificationCenter.default.removeObserver(self) | |
} | |
override func main() { | |
parseContext.performAndWait { | |
self.parsingHandler(self.parseContext) | |
if self.parseContext.hasChanges { | |
do { | |
try self.parseContext.save() | |
CoreDataManager.shared.context.performAndWait { | |
CoreDataManager.shared.saveContext { result in | |
switch result { | |
case .success: | |
break | |
case .failure(let error): | |
assertionFailure(error.localizedDescription) | |
} | |
} | |
} | |
} catch { | |
assertionFailure(error.localizedDescription) | |
} | |
} | |
} | |
} | |
@objc | |
private func managedObjectContextDidSave(_ notification: Notification) { | |
viewContext.performAndWait { | |
viewContext.mergeChanges(fromContextDidSave: notification) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment