import Cocoa
-enum CoreDataManagerType {
- case reader
- case editor
-}
-
-enum CoreDataError: Error {
- case applicationDirectoryIsFile
- case couldNotCreateModel
- case couldNotCreateCoordinator(String)
-}
-
-struct CoreDataIntormation {
+struct CoreDataConfiguration {
+
let modelName: String
let fileName: String
let options: [AnyHashable: Any]
let type: String
+
+ // try remake data file, if model file modified.
let tryRemake: Bool
private static let defaultOptions: [AnyHashable: Any] = [
options: [AnyHashable: Any] = defaultOptions,
type: String = NSSQLiteStoreType,
tryRemake: Bool = false) {
+
self.modelName = modelName
self.fileName = fileName ?? "\(modelName).storedata"
self.options = options
}
struct CoreDataCore {
- let info: CoreDataIntormation
- let model: NSManagedObjectModel
- let coordinator: NSPersistentStoreCoordinator
+
+ let config: CoreDataConfiguration
let parentContext: NSManagedObjectContext
+ private let model: NSManagedObjectModel
+ private let coordinator: NSPersistentStoreCoordinator
- init(_ info: CoreDataIntormation) {
- self.info = info
+ init(_ config: CoreDataConfiguration) {
+
+ self.config = config
+
do {
- let genaratee = try MocGenerater.genarate(info)
- (self.model, self.coordinator, self.parentContext) = genaratee
+
+ (model, coordinator, parentContext) = try MOCGenerator(config).genarate()
+
} 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
- }
-}
-
-
-protocol CoreDataProvider {
- init(type: CoreDataManagerType)
- var core: CoreDataCore { get }
- var context: NSManagedObjectContext { get }
- func save()
-}
-
-protocol CoreDataManager {
- associatedtype InstanceType = Self
-
- static var `default`: InstanceType { get }
- static func oneTimeEditor() -> InstanceType
-
- func removeDataFile()
-}
-
-protocol CoreDataAccessor: CoreDataProvider {
- func insertNewObject<T>(for entity: Entity<T>) -> T?
- func delete(_ object: NSManagedObject)
- func object(with objectId: NSManagedObjectID) -> NSManagedObject
- func objects<T>(with entity: Entity<T>, sortDescriptors: [NSSortDescriptor]?, predicate: NSPredicate?) throws -> [T]
-}
-
-private class CoreDataRemover {
- class func remove(name: String) {
- ["", "-wal", "-shm"]
- .map { name + $0 }
- .map { ApplicationDirecrories.support.appendingPathComponent($0) }
- .forEach { removeDataFile(at: $0) }
- }
- private class func removeDataFile(at url: URL) {
- do {
- try FileManager.default.removeItem(at: url)
- } catch {
- print("Could not remove file for URL (\(url))")
- }
- }
-}
-
-private class MocGenerater {
- class func genarate(_ info: CoreDataIntormation) throws ->
- (model: NSManagedObjectModel, coordinator: NSPersistentStoreCoordinator, moc: NSManagedObjectContext) {
- do {
- let model = try createManagedObjectModel(info)
- let coordinator = try createPersistentStoreCoordinator(info, model)
- let moc = createManagedObjectContext(coordinator)
- return (model: model, coordinator: coordinator, moc: moc)
- } catch {
- throw error
- }
- }
-
- private class func createManagedObjectModel(_ info: CoreDataIntormation) throws -> NSManagedObjectModel {
- let modelURL = Bundle.main.url(forResource: info.modelName, withExtension: "momd")!
- guard let model = NSManagedObjectModel(contentsOf: modelURL) else {
- throw CoreDataError.couldNotCreateModel
- }
- return model
- }
- // swiftlint:disable:next line_length function_body_length
- private class func createPersistentStoreCoordinator(_ info: CoreDataIntormation, _ model: NSManagedObjectModel)
- throws -> NSPersistentStoreCoordinator {
- var failError: NSError? = nil
- var shouldFail = false
- var failureReason = "There was an error creating or loading the application's saved data."
-
- do {
- let p = try ApplicationDirecrories.support.resourceValues(forKeys: [.isDirectoryKey])
- if !p.isDirectory! {
- // swiftlint:disable:next line_length
- failureReason = "Expected a folder to store application data, found a file \(ApplicationDirecrories.support.path)."
- shouldFail = true
- }
- } catch {
- let nserror = error as NSError
- if nserror.code == NSFileReadNoSuchFileError {
- do {
- try FileManager
- .default
- .createDirectory(at: ApplicationDirecrories.support,
- withIntermediateDirectories: false,
- attributes: nil)
- } catch {
- failError = nserror
- }
- } else {
- failError = nserror
- }
- }
- var coordinator: NSPersistentStoreCoordinator? = nil
- if failError == nil {
- coordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
- let url = ApplicationDirecrories.support.appendingPathComponent(info.fileName)
- do {
- try coordinator!.addPersistentStore(ofType: info.type,
- configurationName: nil,
- at: url,
- options: info.options)
- } catch {
- failError = error as NSError
-
- // Data Modelが更新されていたらストアファイルを削除してもう一度
- if failError?.domain == NSCocoaErrorDomain,
- (failError?.code == 134130 || failError?.code == 134110),
- info.tryRemake {
- self.removeDataFile(info)
- do {
- try coordinator!.addPersistentStore(ofType: info.type,
- configurationName: nil,
- at: url,
- options: info.options)
- failError = nil
- } catch {
- failError = error as NSError
- }
- }
- }
- }
-
- if shouldFail || (failError != nil) {
- if let error = failError {
- NSApplication.shared().presentError(error)
- }
- throw CoreDataError.couldNotCreateCoordinator(failureReason)
- }
- return coordinator!
- }
- // swiftlint:disable:next line_length
- private class func createManagedObjectContext(_ coordinator: NSPersistentStoreCoordinator) -> NSManagedObjectContext {
- let moc = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
- moc.persistentStoreCoordinator = coordinator
- moc.undoManager = nil
return moc
}
- private class func removeDataFile(_ info: CoreDataIntormation) {
- CoreDataRemover.remove(name: info.fileName)
- }
-}
-
-extension CoreDataManager where Self: CoreDataProvider {
- func removeDataFile() {
- CoreDataRemover.remove(name: self.core.info.fileName)
- }
-}
-
-extension CoreDataProvider {
- func save() {
- if !context.commitEditing() {
- NSLog("\(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) }
- }
- }
- }
- private func presentOnMainThread(_ error: Error) {
- if Thread.isMainThread {
- NSApp.presentError(error)
- } else {
- DispatchQueue.main.sync {
- let _ = NSApp.presentError(error)
- }
- }
- }
-}
-
-extension CoreDataAccessor {
- func insertNewObject<T>(for entity: Entity<T>) -> 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<T>(with entity: Entity<T>,
- sortDescriptors: [NSSortDescriptor]? = nil,
- predicate: NSPredicate? = nil) throws -> [T] {
- let req = NSFetchRequest<T>(entityName: entity.name)
- req.sortDescriptors = sortDescriptors
- req.predicate = predicate
- return try context.fetch(req)
- }
}