Skip to content

Instantly share code, notes, and snippets.

@SergLam
Last active February 2, 2020 05:46
Show Gist options
  • Save SergLam/ae11e60572f2ec2fc6b054034f6d8253 to your computer and use it in GitHub Desktop.
Save SergLam/ae11e60572f2ec2fc6b054034f6d8253 to your computer and use it in GitHub Desktop.
CoreData manager basic setup
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 }
}
}
}
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)
}
}
}
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