// // CoreDataManager.swift // KCD // // Created by Hori,Masaki on 2017/10/26. // Copyright © 2017年 Hori,Masaki. All rights reserved. // import Cocoa enum CoreDataManagerType { case reader case editor } enum CoreDataError: Error { case saveLocationIsUnuseable case couldNotCreateModel case couldNotCreateCoordinator(String) case couldNotSave(String) } protocol CoreDataProvider { init(type: CoreDataManagerType) static var core: CoreDataCore { get } var context: NSManagedObjectContext { get } func save(errorHandler: @escaping (Error) -> Void) } protocol CoreDataAccessor: CoreDataProvider { func sync(execute: () -> Void) func sync(execute: () -> T) -> T func async(execute: @escaping () -> Void) func insertNewObject(for entity: Entity) -> T? func delete(_ object: NSManagedObject) func object(of entity: Entity, with objectId: NSManagedObjectID) -> T? func objects(of entity: Entity, sortDescriptors: [NSSortDescriptor]?, predicate: NSPredicate?) throws -> [T] func object(with objectId: NSManagedObjectID) -> NSManagedObject } protocol CoreDataManager: CoreDataAccessor { static var `default`: Self { get } static func oneTimeEditor() -> Self } func presentOnMainThread(_ error: Error) { if Thread.isMainThread { NSApp.presentError(error) } else { DispatchQueue.main.sync { _ = NSApp.presentError(error) } } } // MARK: - Extension extension CoreDataProvider { static func context(for type: CoreDataManagerType) -> NSManagedObjectContext { switch type { case .reader: return core.readerContext case .editor: return core.editorContext() } } func save(errorHandler: @escaping (Error) -> Void = presentOnMainThread) { // parentを辿ってsaveしていく func propagateSaveAsync(_ context: NSManagedObjectContext) { guard let parent = context.parent else { return } parent.perform { do { try parent.save() propagateSaveAsync(parent) } catch { errorHandler(error) } } } context.performAndWait { guard context.commitEditing() else { errorHandler(CoreDataError.couldNotSave("Unable to commit editing before saveing")) return } do { try context.save() // save reader and writer context async. // non throw exceptions. propagateSaveAsync(context) } catch let error as NSError { errorHandler(CoreDataError.couldNotSave(error.localizedDescription)) } } } } extension CoreDataAccessor { func sync(execute work: () -> Void) { self.context.performAndWait(work) } func sync(execute work: () -> T) -> T { var value: T! sync { value = work() } return value } func async(execute work: @escaping () -> Void) { self.context.perform(work) } 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(of entity: Entity, with objectId: NSManagedObjectID) -> T? { return context.object(with: objectId) as? T } func objects(of entity: Entity, sortDescriptors: [NSSortDescriptor]? = nil, predicate: NSPredicate? = nil) throws -> [T] { let req = NSFetchRequest(entityName: entity.name) req.sortDescriptors = sortDescriptors req.predicate = predicate var result: [T]? var caughtError: Error? sync { do { result = try self.context.fetch(req) } catch { caughtError = error } } if let error = caughtError { throw error } return result ?? [] } func object(with objectId: NSManagedObjectID) -> NSManagedObject { var result: NSManagedObject? sync { result = self.context.object(with: objectId) } return result! } } extension CoreDataManager { static func oneTimeEditor() -> Self { return Self.init(type: .editor) } }