2 // CoreDataManager.swift
5 // Created by Hori,Masaki on 2017/02/05.
6 // Copyright © 2017年 Hori,Masaki. All rights reserved.
11 enum CoreDataManagerType {
16 enum CoreDataError: Error {
17 case applicationDirectoryIsFile
18 case couldNotCreateModel
19 case couldNotCreateCoordinator(String)
22 struct CoreDataIntormation {
25 let options: [AnyHashable: Any]
29 private static let defaultOptions: [AnyHashable: Any] = [
30 NSMigratePersistentStoresAutomaticallyOption: true,
31 NSInferMappingModelAutomaticallyOption: true
34 init(_ modelName: String,
35 fileName: String? = nil,
36 options: [AnyHashable: Any] = defaultOptions,
37 type: String = NSSQLiteStoreType,
38 tryRemake: Bool = false) {
39 self.modelName = modelName
40 self.fileName = fileName ?? "\(modelName).storedata"
41 self.options = options
43 self.tryRemake = tryRemake
48 let info: CoreDataIntormation
49 let model: NSManagedObjectModel
50 let coordinator: NSPersistentStoreCoordinator
51 let parentContext: NSManagedObjectContext
53 init(_ info: CoreDataIntormation) {
56 let genaratee = try MocGenerater.genarate(info)
57 (self.model, self.coordinator, self.parentContext) = genaratee
59 fatalError("CoreDataCore: can not initialize. \(error)")
63 func editorContext() -> NSManagedObjectContext {
64 let moc = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
65 moc.parent = parentContext
72 protocol CoreDataProvider {
73 init(type: CoreDataManagerType)
74 var core: CoreDataCore { get }
75 var context: NSManagedObjectContext { get }
79 protocol CoreDataManager {
80 associatedtype InstanceType = Self
82 static var `default`: InstanceType { get }
83 static func oneTimeEditor() -> InstanceType
88 protocol CoreDataAccessor: CoreDataProvider {
89 func insertNewObject<T>(for entity: Entity<T>) -> T?
90 func delete(_ object: NSManagedObject)
91 func object(with objectId: NSManagedObjectID) -> NSManagedObject
92 func objects<T>(with entity: Entity<T>, sortDescriptors: [NSSortDescriptor]?, predicate: NSPredicate?) throws -> [T]
95 private class CoreDataRemover {
96 class func remove(name: String) {
99 .map { ApplicationDirecrories.support.appendingPathComponent($0) }
100 .forEach { removeDataFile(at: $0) }
102 private class func removeDataFile(at url: URL) {
104 try FileManager.default.removeItem(at: url)
106 print("Could not remove file for URL (\(url))")
111 private class MocGenerater {
112 class func genarate(_ info: CoreDataIntormation) throws ->
113 (model: NSManagedObjectModel, coordinator: NSPersistentStoreCoordinator, moc: NSManagedObjectContext) {
115 let model = try createManagedObjectModel(info)
116 let coordinator = try createPersistentStoreCoordinator(info, model)
117 let moc = createManagedObjectContext(coordinator)
118 return (model: model, coordinator: coordinator, moc: moc)
124 private class func createManagedObjectModel(_ info: CoreDataIntormation) throws -> NSManagedObjectModel {
125 let modelURL = Bundle.main.url(forResource: info.modelName, withExtension: "momd")!
126 guard let model = NSManagedObjectModel(contentsOf: modelURL) else {
127 throw CoreDataError.couldNotCreateModel
131 // swiftlint:disable:next line_length function_body_length
132 private class func createPersistentStoreCoordinator(_ info: CoreDataIntormation, _ model: NSManagedObjectModel)
133 throws -> NSPersistentStoreCoordinator {
134 var failError: NSError? = nil
135 var shouldFail = false
136 var failureReason = "There was an error creating or loading the application's saved data."
139 let p = try ApplicationDirecrories.support.resourceValues(forKeys: [.isDirectoryKey])
141 // swiftlint:disable:next line_length
142 failureReason = "Expected a folder to store application data, found a file \(ApplicationDirecrories.support.path)."
146 let nserror = error as NSError
147 if nserror.code == NSFileReadNoSuchFileError {
151 .createDirectory(at: ApplicationDirecrories.support,
152 withIntermediateDirectories: false,
162 var coordinator: NSPersistentStoreCoordinator? = nil
163 if failError == nil {
164 coordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
165 let url = ApplicationDirecrories.support.appendingPathComponent(info.fileName)
167 try coordinator!.addPersistentStore(ofType: info.type,
168 configurationName: nil,
170 options: info.options)
172 failError = error as NSError
174 // Data Modelが更新されていたらストアファイルを削除してもう一度
175 if failError?.domain == NSCocoaErrorDomain,
176 (failError?.code == 134130 || failError?.code == 134110),
178 self.removeDataFile(info)
180 try coordinator!.addPersistentStore(ofType: info.type,
181 configurationName: nil,
183 options: info.options)
186 failError = error as NSError
192 if shouldFail || (failError != nil) {
193 if let error = failError {
194 NSApplication.shared().presentError(error)
196 throw CoreDataError.couldNotCreateCoordinator(failureReason)
200 // swiftlint:disable:next line_length
201 private class func createManagedObjectContext(_ coordinator: NSPersistentStoreCoordinator) -> NSManagedObjectContext {
202 let moc = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
203 moc.persistentStoreCoordinator = coordinator
204 moc.undoManager = nil
207 private class func removeDataFile(_ info: CoreDataIntormation) {
208 CoreDataRemover.remove(name: info.fileName)
212 extension CoreDataManager where Self: CoreDataProvider {
213 func removeDataFile() {
214 CoreDataRemover.remove(name: self.core.info.fileName)
218 extension CoreDataProvider {
220 if !context.commitEditing() {
221 NSLog("\(String(describing: type(of: self))) unable to commit editing before saveing")
226 } catch { presentOnMainThread(error) }
227 if let p = context.parent {
231 } catch { self.presentOnMainThread(error) }
235 private func presentOnMainThread(_ error: Error) {
236 if Thread.isMainThread {
237 NSApp.presentError(error)
239 DispatchQueue.main.sync {
240 let _ = NSApp.presentError(error)
246 extension CoreDataAccessor {
247 func insertNewObject<T>(for entity: Entity<T>) -> T? {
248 return NSEntityDescription
249 .insertNewObject(forEntityName: entity.name,
252 func delete(_ object: NSManagedObject) {
253 context.delete(object)
255 func object(with objectId: NSManagedObjectID) -> NSManagedObject {
256 return context.object(with: objectId)
258 func objects<T>(with entity: Entity<T>,
259 sortDescriptors: [NSSortDescriptor]? = nil,
260 predicate: NSPredicate? = nil) throws -> [T] {
261 let req = NSFetchRequest<T>(entityName: entity.name)
262 req.sortDescriptors = sortDescriptors
263 req.predicate = predicate
264 return try context.fetch(req)