// // CoreDataManager.swift // KCD // // Created by Hori,Masaki on 2017/02/05. // Copyright © 2017年 Hori,Masaki. All rights reserved. // import Cocoa // MARK: enum enum CoreDataManagerType { case reader case editor } // MARK: - struct struct CoreDataConfiguration { let modelName: String let fileName: String let options: [AnyHashable: Any] let type: String let tryRemake: Bool private static let defaultOptions: [AnyHashable: Any] = [ NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true ] init(_ modelName: String, fileName: String? = nil, options: [AnyHashable: Any] = defaultOptions, type: String = NSSQLiteStoreType, tryRemake: Bool = false) { self.modelName = modelName self.fileName = fileName ?? "\(modelName).storedata" self.options = options self.type = type self.tryRemake = tryRemake } } struct CoreDataCore { let config: CoreDataConfiguration let parentContext: NSManagedObjectContext private let model: NSManagedObjectModel private let coordinator: NSPersistentStoreCoordinator init(_ config: CoreDataConfiguration) { self.config = config do { (model, coordinator, parentContext) = try genarate(config) } catch { NSApplication.shared().presentError(error) fatalError("CoreDataCore: can not initialize. \(error)") } } func editorContext() -> NSManagedObjectContext { let moc = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) moc.parent = parentContext moc.undoManager = nil return moc } } // MARK: - protocol protocol CoreDataProvider { init(type: CoreDataManagerType) static var core: CoreDataCore { get } var context: NSManagedObjectContext { get } func save() func removeDataFile() } protocol CoreDataAccessor: CoreDataProvider { func insertNewObject(for entity: Entity) -> T? func delete(_ object: NSManagedObject) func object(with objectId: NSManagedObjectID) -> NSManagedObject func objects(with entity: Entity, sortDescriptors: [NSSortDescriptor]?, predicate: NSPredicate?) throws -> [T] } protocol CoreDataManager { associatedtype InstanceType = Self static var `default`: InstanceType { get } static func oneTimeEditor() -> InstanceType } // MARK: - Extension extension CoreDataProvider { func save() { if !context.commitEditing() { print("\(String(describing: type(of: self))) unable to commit editing before saveing") return } do { try context.save() } catch { presentOnMainThread(error) } if let p = context.parent { p.performAndWait { do { try p.save() } catch { self.presentOnMainThread(error) } } } } func removeDataFile() { remove(name: type(of: self).core.config.fileName) } private func presentOnMainThread(_ error: Error) { if Thread.isMainThread { NSApp.presentError(error) } else { DispatchQueue.main.sync { let _ = NSApp.presentError(error) } } } static func context(for type: CoreDataManagerType) -> NSManagedObjectContext { switch type { case .reader: return core.parentContext case .editor: return core.editorContext() } } } extension CoreDataAccessor { func insertNewObject(for entity: Entity) -> T? { return NSEntityDescription.insertNewObject(forEntityName: entity.name, into: context) as? T } func delete(_ object: NSManagedObject) { context.delete(object) } func object(with objectId: NSManagedObjectID) -> NSManagedObject { return context.object(with: objectId) } func objects(with entity: Entity, sortDescriptors: [NSSortDescriptor]? = nil, predicate: NSPredicate? = nil) throws -> [T] { let req = NSFetchRequest(entityName: entity.name) req.sortDescriptors = sortDescriptors req.predicate = predicate return try context.fetch(req) } }